A maneira canônica de retornar vários valores em idiomas que o suportam é geralmente a tupla .
Opção: usando uma tupla
Considere este exemplo trivial:
def f(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return (y0, y1, y2)
No entanto, isso rapidamente se torna problemático à medida que o número de valores retornados aumenta. E se você quiser retornar quatro ou cinco valores? Claro, você pode continuar os tupleando, mas fica fácil esquecer qual é o valor. Também é muito feio desembalá-los onde você quiser recebê-los.
Opção: usando um dicionário
O próximo passo lógico parece ser a introdução de algum tipo de 'notação de registro'. No Python, a maneira óbvia de fazer isso é por meio de a dict
.
Considere o seguinte:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0': y0, 'y1': y1 ,'y2': y2}
(Só para esclarecer, y0, y1 e y2 são apenas identificadores abstratos. Como apontado, na prática, você usaria identificadores significativos.)
Agora, temos um mecanismo pelo qual podemos projetar um membro específico do objeto retornado. Por exemplo,
result['y0']
Opção: usando uma classe
No entanto, há outra opção. Em vez disso, poderíamos retornar uma estrutura especializada. Enquadrei isso no contexto do Python, mas tenho certeza que se aplica a outras linguagens também. De fato, se você estivesse trabalhando em C, essa poderia ser sua única opção. Aqui vai:
class ReturnValue:
def __init__(self, y0, y1, y2):
self.y0 = y0
self.y1 = y1
self.y2 = y2
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return ReturnValue(y0, y1, y2)
No Python, os dois anteriores são talvez muito semelhantes em termos de encanamento - afinal { y0, y1, y2 }
acabam sendo entradas no interior __dict__
do ReturnValue
.
Há um recurso adicional fornecido pelo Python para objetos minúsculos, o __slots__
atributo A classe pode ser expressa como:
class ReturnValue(object):
__slots__ = ["y0", "y1", "y2"]
def __init__(self, y0, y1, y2):
self.y0 = y0
self.y1 = y1
self.y2 = y2
No manual de referência do Python :
A
__slots__
declaração utiliza uma sequência de variáveis de instância e reserva espaço suficiente em cada instância para armazenar um valor para cada variável. O espaço é salvo porque__dict__
não é criado para cada instância.
Opção: usando uma classe de dados (Python 3.7+)
Usando as novas classes de dados do Python 3.7, retorne uma classe com métodos especiais adicionados automaticamente, digitação e outras ferramentas úteis:
@dataclass
class Returnvalue:
y0: int
y1: float
y3: int
def total_cost(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return ReturnValue(y0, y1, y2)
Opção: Usando uma lista
Outra sugestão que eu tinha esquecido vem de Bill, o Lagarto:
def h(x):
result = [x + 1]
result.append(x * 3)
result.append(y0 ** y3)
return result
Este é o meu método menos favorito, no entanto. Suponho que estou contaminado pela exposição a Haskell, mas a ideia de listas de tipos mistos sempre me pareceu desconfortável. Neste exemplo em particular, a lista não é do tipo misto, mas pode ser concebível.
Uma lista usada dessa maneira realmente não ganha nada em relação à tupla, até onde eu sei. A única diferença real entre listas e tuplas no Python é que as listas são mutáveis , enquanto as tuplas não são.
Pessoalmente, tenho a tendência de manter as convenções da programação funcional: use listas para qualquer número de elementos do mesmo tipo e tuplas para um número fixo de elementos de tipos predeterminados.
Questão
Após o preâmbulo longo, vem a pergunta inevitável. Qual método (você acha) é o melhor?
fonte
y3
, mas, a menos que y3 seja declarado global, isso renderiaNameError: global name 'y3' is not defined
talvez apenas usar3
?y3
, que também fará o mesmo trabalho.Respostas:
As tuplas nomeadas foram adicionadas no 2.6 para esse fim. Veja também os.stat para um exemplo embutido similar.
Nas versões recentes do Python 3 (3.6+, acho), a nova
typing
biblioteca fez com que aNamedTuple
classe tornasse as tuplas nomeadas mais fáceis de criar e mais poderosas. Herdar detyping.NamedTuple
permite usar cadeias de documentos, valores padrão e anotações de tipo.Exemplo (dos documentos):
fonte
namedtuple
é ter uma área de memória menor para resultados em massa (longas listas de tuplas, como resultados de consultas ao banco de dados). Para itens individuais (se a função em questão não é chamada com freqüência), dicionários e classes também estão bem. Mas os nomeados são também uma solução agradável / melhor neste caso.namedtuple
definições (cada chamada cria uma nova), a criação danamedtuple
classe é relativamente cara na CPU e na memória, e todas as definições de classe envolvem intrinsecamente referências cíclicas (no CPython, você está aguardando uma execução cíclica do GC para que eles sejam liberados). Isso também torna impossível parapickle
a classe (e, portanto, impossível usar instânciasmultiprocessing
na maioria dos casos). Cada criação da classe no meu 3.6.4 x64 consome ~ 0.337 ms e ocupa pouco menos de 1 KB de memória, eliminando qualquer economia de instância.namedtuple
classes ; os custos de CPU caem aproximadamente um fator de 4x , mas ainda são aproximadamente 1000x mais altos do que o custo para criar uma instância, e o custo de memória para cada classe permanece alto (eu estava errado no meu último comentário sobre "menos de 1 KB" para a classe,_source
por si só, normalmente é de 1,5 KB;_source
é removido na versão 3.7, portanto, provavelmente está mais perto da reivindicação original de pouco menos de 1 KB por criação de classe).Para pequenos projetos, acho mais fácil trabalhar com tuplas. Quando isso fica muito difícil de gerenciar (e não antes), começo a agrupar as coisas em estruturas lógicas, mas acho que o uso sugerido de dicionários e
ReturnValue
objetos está errado (ou simplista).Retornando um dicionário com chaves
"y0"
,"y1"
,"y2"
, etc. não oferecem qualquer vantagem sobre tuplas. Retornando umaReturnValue
instância com propriedades.y0
,.y1
,.y2
, etc. não oferecem qualquer vantagem sobre tuplas também. Você precisa começar a nomear as coisas, se quiser chegar a algum lugar, e pode fazer isso usando tuplas de qualquer maneira:IMHO, a única boa técnica além das tuplas é retornar objetos reais com métodos e propriedades adequados, como você obtém de
re.match()
ouopen(file)
.fonte
size, type, dimensions = getImageData(x)
e(size, type, dimensions) = getImageData(x)
? Ou seja, o acondicionamento do lado esquerdo de uma tarefa com tuplas faz alguma diferença?(1)
é um int while(1,)
ou1,
é uma tupla.Muitas respostas sugerem que você precisa retornar uma coleção de algum tipo, como um dicionário ou uma lista. Você pode deixar de fora a sintaxe extra e escrever os valores de retorno separados por vírgula. Nota: isso tecnicamente retorna uma tupla.
dá:
fonte
type(f())
retorna<class 'tuple'>
.tuple
aspecto explícito; não é realmente importante que você retorne umtuple
, este é o idioma para retornar um período de vários valores. O mesmo motivo pelo qual você omite os parênteses com o idioma de trocax, y = y, x
, inicialização múltiplax, y = 0, 1
, etc .; Certamente, ele criatuple
s sob o capô, mas não há razão para explicitar isso, pois ostuple
s não são o ponto. O tutorial do Python apresenta várias atribuições muito antes de tocar emtuple
s.=
uma tupla em Python com ou sem parênteses. Portanto, não há realmente explícito ou implícito aqui. a, b, c é tanto uma tupla quanto (a, b, c). Também não há criação de tupla "oculta" quando você retorna esses valores, porque é apenas uma tupla simples e simples. O OP já mencionou tuplas, portanto não há realmente nenhuma diferença entre o que ele mencionou e o que esta resposta mostra. NenhumEu voto no dicionário.
Acho que, se eu criar uma função que retorne mais do que 2-3 variáveis, eu as dobrarei em um dicionário. Caso contrário, costumo esquecer a ordem e o conteúdo do que estou retornando.
Além disso, a introdução de uma estrutura 'especial' torna seu código mais difícil de seguir. (Outra pessoa precisará pesquisar o código para descobrir o que é)
Se você estiver preocupado com a pesquisa de tipo, use chaves de dicionário descritivas, por exemplo, 'lista de valores x'.
fonte
result = g(x); other_function(result)
Outra opção seria usar geradores:
Embora as tuplas do IMHO sejam geralmente as melhores, exceto nos casos em que os valores retornados são candidatos ao encapsulamento em uma classe.
fonte
yield
?def f(x): …; yield b; yield a; yield r
vs.(g for g in [b, a, r])
e ambos serão facilmente convertidos em listas ou tuplas e, como tal, suportarão a descompactação de tuplas. A forma do gerador de tupla segue uma abordagem funcional, enquanto a forma da função é imperativa e permitirá controle de fluxo e atribuição de variáveis.Eu prefiro usar tuplas sempre que ela parecer "natural"; As coordenadas são um exemplo típico, em que os objetos separados podem ficar por conta própria, por exemplo, em cálculos de escala de um eixo apenas, e a ordem é importante. Nota: se eu puder classificar ou embaralhar os itens sem afetar adversamente o significado do grupo, provavelmente não devo usar uma tupla.
Só uso dicionários como valor de retorno quando os objetos agrupados nem sempre são os mesmos. Pense em cabeçalhos de email opcionais.
Nos demais casos, onde os objetos agrupados têm um significado inerente dentro do grupo ou é necessário um objeto completo com seus próprios métodos, eu uso uma classe.
fonte
Eu prefiro:
Parece que tudo o resto é apenas código extra para fazer a mesma coisa.
fonte
fonte
Geralmente, a "estrutura especializada" é realmente um estado atual sensível de um objeto, com seus próprios métodos.
Eu gosto de encontrar nomes para estruturas anônimas sempre que possível. Nomes significativos tornam as coisas mais claras.
fonte
As tuplas, dictos e objetos do Python oferecem ao programador uma troca suave entre formalidade e conveniência para pequenas estruturas de dados ("coisas"). Para mim, a escolha de como representar uma coisa é ditada principalmente pelo modo como vou usar a estrutura. No C ++, é uma convenção comum usar
struct
itens somente de dados eclass
objetos com métodos, mesmo que você possa legalmente colocar métodos em umstruct
; meu hábito é semelhante em Python, comdict
etuple
no lugar destruct
.Para conjuntos de coordenadas, usarei
tuple
um ponto em vez de um pontoclass
ou umdict
(e observe que você pode usar atuple
como uma chave de dicionário, portanto,dict
são excelentes matrizes multidimensionais esparsas).Se vou repetir uma lista de coisas, prefiro descompactar
tuple
s na iteração:... como a versão do objeto é mais confusa para ler:
... e muito menos o
dict
.Se a coisa é amplamente usada e você se encontra realizando operações não triviais semelhantes em vários locais do código, geralmente vale a pena torná-la um objeto de classe com métodos apropriados.
Por fim, se eu vou trocar dados com componentes de sistema que não sejam Python, na maioria das vezes os manterei em um
dict
porque é mais adequado à serialização JSON.fonte
+1 na sugestão de S.Lott de uma classe de contêiner nomeada.
Para o Python 2.6 ou superior, uma tupla nomeada fornece uma maneira útil de criar facilmente essas classes de contêineres, e os resultados são "leves e não requerem mais memória que as tuplas regulares".
fonte
Em idiomas como Python, eu usaria um dicionário, pois envolve menos sobrecarga do que a criação de uma nova classe.
No entanto, se eu me encontrar constantemente retornando o mesmo conjunto de variáveis, isso provavelmente envolve uma nova classe que eu fatorarei.
fonte
Eu usaria um dict para passar e retornar valores de uma função:
Use o formulário variável conforme definido no formulário .
Essa é a maneira mais eficiente para mim e para a unidade de processamento.
Você precisa passar apenas um ponteiro e retornar apenas um ponteiro.
Você não precisa alterar os argumentos das funções (milhares deles) sempre que fizer uma alteração no seu código.
fonte
test
modificará diretamente o valor. Compare isso comdict.update
, que não retorna um valor.form
não prejudica a legibilidade nem o desempenho. Nos casos em que você pode precisar reformatarform
, devolvê-lo [formulário] garante que o últimoform
seja retornado, pois você não acompanhará as alterações no formulário em nenhum lugar."Melhor" é uma decisão parcialmente subjetiva. Use tuplas para pequenos conjuntos de retorno no caso geral em que um imutável é aceitável. Uma tupla é sempre preferível a uma lista quando a mutabilidade não é um requisito.
Para valores de retorno mais complexos ou para o caso em que a formalidade é valiosa (ou seja, código de alto valor), uma tupla nomeada é melhor. Para o caso mais complexo, um objeto geralmente é o melhor. No entanto, é realmente a situação que importa. Se faz sentido retornar um objeto, porque é isso que você possui naturalmente no final da função (por exemplo, padrão de fábrica), retorne o objeto.
Como o sábio disse:
fonte