Como faço para retornar vários valores de uma função? [fechadas]

1067

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?

saffsd
fonte
10
Em seus excelentes exemplos, você usa variáveis y3, mas, a menos que y3 seja declarado global, isso renderia NameError: global name 'y3' is not definedtalvez apenas usar 3?
hetepeperfan
11
Muitas ótimas perguntas com ótimas respostas são fechadas porque a palavra-chave 'opinião' surge. Você poderia argumentar que todo o SO é baseado em opinião, mas é uma opinião informada por fatos, referências e conhecimentos específicos. Só porque alguém pergunta "o que você acha melhor" não significa que está pedindo opiniões pessoais abstraídas de fatos, referências e conhecimentos específicos do mundo real. Eles quase certamente pedem exatamente esse tipo de opinião, o tipo com base completa e documentada com os fatos, referências e conhecimentos específicos que a pessoa usou para formar a opinião.
NeilG 8/08/19
@hetepeperfan não há necessidade de alterar 3, e nem a definição de y3 no global, você também pode usar um nome local y3, que também fará o mesmo trabalho.
okie

Respostas:

637

As tuplas nomeadas foram adicionadas no 2.6 para esse fim. Veja também os.stat para um exemplo embutido similar.

>>> import collections
>>> Point = collections.namedtuple('Point', ['x', 'y'])
>>> p = Point(1, y=2)
>>> p.x, p.y
1 2
>>> p[0], p[1]
1 2

Nas versões recentes do Python 3 (3.6+, acho), a nova typingbiblioteca fez com que a NamedTupleclasse tornasse as tuplas nomeadas mais fáceis de criar e mais poderosas. Herdar de typing.NamedTuplepermite usar cadeias de documentos, valores padrão e anotações de tipo.

Exemplo (dos documentos):

class Employee(NamedTuple):  # inherit from typing.NamedTuple
    name: str
    id: int = 3  # default value

employee = Employee('Guido')
assert employee.id == 3
A. Coady
fonte
68
Essa é apenas a resposta correta, porque é a única estrutura canônica que o OP não considerou e porque resolve o problema de gerenciar tuplas longas. Deve ser marcado como aceito.
Julio
7
Bem, a lógica do design 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.
Lutz Prechelt 23/02
8
@ wom: Não faça isso. O Python não faz nenhum esforço para unificar namedtupledefinições (cada chamada cria uma nova), a criação da namedtupleclasse é 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 para picklea classe (e, portanto, impossível usar instâncias multiprocessingna 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.
ShadowRanger
3
Observarei que o Python 3.7 melhorou a velocidade de criação de novas namedtupleclasses ; 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, _sourcepor 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).
ShadowRanger
4
@SergeStroobandt - Isso faz parte da biblioteca padrão, mas não é um builtin. Você não precisa se preocupar que ele pode não estar instalado em outro sistema com Python> = 2.6. Ou você apenas se opõe à linha extra de código?
Justin
234

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 ReturnValueobjetos está errado (ou simplista).

Retornando um dicionário com chaves "y0", "y1", "y2", etc. não oferecem qualquer vantagem sobre tuplas. Retornando uma ReturnValueinstâ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:

def get_image_data(filename):
    [snip]
    return size, (format, version, compression), (width,height)

size, type, dimensions = get_image_data(x)

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()ou open(file).

muito php
fonte
6
Pergunta - existe alguma diferença entre 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?
Reb.Cabin
11
@ Reb.Cabin Não há diferença. A tupla é identificada por vírgula e o uso de parênteses é apenas para agrupar as coisas. Por exemplo, (1)é um int while (1,)ou 1,é uma tupla.
phil
19
Re "retornar um dicionário com as teclas y0, y1, y2 etc não oferece nenhuma vantagem sobre as tuplas": o dicionário tem a vantagem de poder adicionar campos ao dicionário retornado sem quebrar o código existente.
ostrokach
Re "retornar um dicionário com as teclas y0, y1, y2 etc não oferece nenhuma vantagem sobre as tuplas": também é mais legível e menos suscetível a erros quando você acessa os dados com base no nome e não na posição.
Denis Dollfus
204

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.

def f():
    return True, False
x, y = f()
print(x)
print(y)

dá:

True
False
Joseph Hansen
fonte
24
Você ainda está retornando uma coleção. É uma tupla. Prefiro parênteses para torná-lo mais explícito. Tente o seguinte: type(f())retorna <class 'tuple'>.
Igor
20
@ Igor: Não há razão para tornar o tupleaspecto explícito; não é realmente importante que você retorne um tuple, 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 troca x, y = y, x, inicialização múltipla x, y = 0, 1, etc .; Certamente, ele cria tuples sob o capô, mas não há razão para explicitar isso, pois os tuples não são o ponto. O tutorial do Python apresenta várias atribuições muito antes de tocar em tuples.
ShadowRanger
@ShadowRanger qualquer sequência de valores separados por vírgula no lado direito de =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. Nenhum
Ken4scholars
2
Esta é literalmente a primeira opção sugerida na pergunta
endólito
1
@endolith As duas vezes em que o cara faz uma pergunta ("Como retorno vários valores?" e "Como você retorna vários valores?") são respondidas por esta resposta. O texto da pergunta mudou algumas vezes. E é uma pergunta baseada em opinião.
Joseph Hansen
74

Eu 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'.

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }
monkut
fonte
5
depois de muitos anos de programação, tenho tendência para o que for necessário para a estrutura de dados e função. Função primeiro, você sempre pode refatorar conforme necessário.
monkut
Como obteríamos os valores dentro do dicionário sem chamar a função várias vezes? Por exemplo, se eu quiser usar y1 e y3 em uma função diferente?
Matt
3
atribua os resultados a uma variável separada. result = g(x); other_function(result)
Monkut 04/11
1
@monkut yes. Dessa maneira, também é possível passar o resultado para várias funções, que levam argumentos diferentes do resultado, sem precisar fazer referência específica a partes específicas do resultado todas as vezes.
Gnudiff
38

Outra opção seria usar geradores:

>>> def f(x):
        y0 = x + 1
        yield y0
        yield x * 3
        yield y0 ** 4


>>> a, b, c = f(5)
>>> a
6
>>> b
15
>>> c
1296

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.

rlms
fonte
1
Essa parece ser a solução mais limpa e possui uma sintaxe limpa. Há alguma desvantagem nisso? Se você não usar todos os retornos, há rendimentos "não gastos" esperando para machucá-lo?
Jiminion
24
Isso pode ser "limpo", mas não parece intuitivo. Como alguém que nunca encontrou esse padrão antes saberia que a descompactação automática de tuplas acionaria cada um yield?
Coredumperror
1
@CoreDumpError, geradores são apenas isso ... geradores. Não há diferença externa entre def f(x): …; yield b; yield a; yield rvs. (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.
sleblanc 28/01/19
30

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.

tzot
fonte
29

Eu prefiro:

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }

Parece que tudo o resto é apenas código extra para fazer a mesma coisa.

UnkwnTech
fonte
22
É fácil descompactar as tuplas: y0, y1, y2 = g () com um ditado que você deve fazer: resultado = g () y0, y1, y2 = resultado.get ('y0'), resultado.get ('y1' ), result.get ('y2'), que é um pouco feio. Cada solução tem seus 'prós e contras'.
Oli
27
>>> def func():
...    return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3
WebQube
fonte
@edouard Não, não, ele retorna uma tupla, não uma lista.
Simon Hibbs 17/02
1
desestruturação é o argumento para retornar listas na minha opinião
semiomant
21

Geralmente, a "estrutura especializada" é realmente um estado atual sensível de um objeto, com seus próprios métodos.

class Some3SpaceThing(object):
  def __init__(self,x):
    self.g(x)
  def g(self,x):
    self.y0 = x + 1
    self.y1 = x * 3
    self.y2 = y0 ** y3

r = Some3SpaceThing( x )
r.y0
r.y1
r.y2

Eu gosto de encontrar nomes para estruturas anônimas sempre que possível. Nomes significativos tornam as coisas mais claras.

S.Lott
fonte
20

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 structitens somente de dados eclass objetos com métodos, mesmo que você possa legalmente colocar métodos em um struct; meu hábito é semelhante em Python, com dicte tupleno lugar destruct .

Para conjuntos de coordenadas, usarei tupleum ponto em vez de um pontoclass ou um dict(e observe que você pode usar a tuplecomo uma chave de dicionário, portanto, dictsão excelentes matrizes multidimensionais esparsas).

Se vou repetir uma lista de coisas, prefiro descompactar tuple s na iteração:

for score,id,name in scoreAllTheThings():
    if score > goodScoreThreshold:
        print "%6.3f #%6d %s"%(score,id,name)

... como a versão do objeto é mais confusa para ler:

for entry in scoreAllTheThings():
    if entry.score > goodScoreThreshold:
        print "%6.3f #%6d %s"%(entry.score,entry.id,entry.name)

... e muito menos o dict .

for entry in scoreAllTheThings():
    if entry['score'] > goodScoreThreshold:
        print "%6.3f #%6d %s"%(entry['score'],entry['id'],entry['name'])

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 dictporque é mais adequado à serialização JSON.

Russell Borogove
fonte
19

+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".

John Fouhy
fonte
4

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.

fluffels
fonte
4

Eu usaria um dict para passar e retornar valores de uma função:

Use o formulário variável conforme definido no formulário .

form = {
    'level': 0,
    'points': 0,
    'game': {
        'name': ''
    }
}


def test(form):
    form['game']['name'] = 'My game!'
    form['level'] = 2

    return form

>>> print(test(form))
{u'game': {u'name': u'My game!'}, u'points': 0, u'level': 2}

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.

Elis Byberi
fonte
Os ditados são mutáveis. Se você passar um ditado para uma função e essa função o editar, as alterações serão refletidas fora do escopo dessa função. Ter a função retornando o ditado no final pode implicar que a função não tem efeitos colaterais; portanto, o valor não deve ser retornado, deixando claro que testmodificará diretamente o valor. Compare isso com dict.update, que não retorna um valor.
sleblanc 28/01/19
@sleblanc "Ter a função retornando o ditado no final pode implicar que a função não tenha efeitos colaterais". Isso não implica isso porque, como você disse, o ditado é mutável. No entanto, retornar formnão prejudica a legibilidade nem o desempenho. Nos casos em que você pode precisar reformatar form, devolvê-lo [formulário] garante que o último formseja retornado, pois você não acompanhará as alterações no formulário em nenhum lugar.
Elis Byberi 28/01/19
3

"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:

A otimização prematura é a raiz de todos os males (ou pelo menos a maioria deles) na programação.

joel3000
fonte
1
Donald Knuth disse isso (em 1974).
Peter Mortensen