Existe uma convenção para devolver vários itens?


10

No Python especificamente (não sei se isso generaliza), existe uma "melhor" maneira de retornar vários itens de uma função?

def func1():
    return a,b #equivalent to (a,b)

def func2():
    return[a,b]

def func3():
    return{"valueA":a,"valueB":b}

O primeiro é o que vejo de maneira mais geral, mas sinto que o último cria um código mais legível porque você nomeou saídas, mas posso estar perdendo algum tipo de sobrecarga criada por esse método?


Você não está ajustando vários argumentos; em todos esses exemplos, você retornará um único resultado que, por acaso, é um tipo de dados composto (uma tupla, lista e dicionário, respectivamente). O tipo de dado a ser retornado depende da sua função e como ela deve ser usada.
gardenhead

Interessante, eu nunca pensei nisso dessa forma, se você fizer que uma resposta que pode aceitá-la
Devon Muraoka

Respostas:


13

Eu acho que as escolhas precisam ser consideradas estritamente do ponto de vista do interlocutor: o que o consumidor provavelmente precisará fazer?

E quais são as principais características de cada coleção?

  • A tupla é acessada em ordem e imutável
  • A lista é acessada em ordem e mutável
  • O ditado é acessado por chave

A lista e a tupla são equivalentes para acesso, mas a lista é mutável. Bem, isso não importa para mim, se eu vou descompactar imediatamente os resultados:

score, top_player = play_round(players)
# or
idx, record = find_longest(records)

Não há nenhuma razão para eu me importar se é uma lista ou uma tupla, e a tupla é mais simples nos dois lados.

Por outro lado, se a coleção retornada for mantida inteira e usada como coleção :

points = calculate_vertices(shape)
points.append(another_point)
# Make a new shape

então pode fazer sentido que o retorno seja mutável. A homogeneidade também é um fator importante aqui. Digamos que você tenha escrito uma função para procurar padrões repetidos em uma sequência. As informações que recebo são o índice na sequência da primeira instância do padrão, o número de repetições e o próprio padrão. Esses não são os mesmos tipos de coisa. Mesmo que eu possa manter as peças juntas, não há razão para querer alterar a coleção . Isto não é um list.

Agora para o dicionário.

o último cria um código mais legível porque você nomeou saídas

Sim, ter chaves para os campos torna os dados heterogêneos mais explícitos, mas também vêm com alguns ônus. Mais uma vez, no caso de "apenas vou descompactar as coisas", isso

round_results = play_round(players)
score, top_player = round_results["score"], round_results["top_player"]

(mesmo que você evite cadeias literais para as chaves), é um trabalho desnecessário em comparação com a versão da tupla.

A questão aqui é tríplice: quão complexa é a coleção, por quanto tempo a coleção será mantida unida e precisaremos usar esse mesmo tipo de coleção em vários lugares diferentes?

Eu sugiro que um valor de retorno de acesso com chave comece a fazer mais sentido do que uma tupla quando houver mais de três membros e, especialmente, onde houver aninhamento:

shape["transform"]["raw_matrix"][0, 1] 
# vs.
shape[2][4][0, 1]

Isso leva à próxima pergunta: a coleção deixará esse escopo intacto, longe da chamada que o criou? O acesso com chave por lá ajudará absolutamente a compreensão.

A terceira pergunta - reutilização - aponta para um tipo de dados personalizado simples como uma quarta opção que você não apresentou.

A estrutura é de propriedade exclusiva dessa função? Ou você está criando o mesmo layout de dicionário em muitos lugares? Muitas outras partes do programa precisam operar nessa estrutura? Um layout de dicionário repetido deve ser fatorado para uma classe. O bônus é que você pode anexar um comportamento: talvez algumas das funções que operam nos dados sejam encapsuladas como métodos.

Uma quinta opção boa e leve é namedtuple(). Essa é essencialmente a forma imutável do valor de retorno do dicionário.


Existe um motivo para você não mencionar instâncias de classes definidas pelo usuário? Essa deve ser a solução Goto em qualquer idioma OO.
Esben Skov Pedersen

1
Isso é coberto nos parágrafos terceiro e penúltimo, @EsbenSkovPedersen. "Um layout de dicionário repetido deve ser fatorado para uma classe."
JSCs

1

Não pense nas funções que retornam vários argumentos. Conceitualmente, é melhor pensar nas funções como recebendo e retornando um único argumento. Uma função que parece aceitar vários argumentos, na verdade, recebe apenas um único argumento do tipo tupla ( produto formalmente ). Da mesma forma, uma função que retorna vários argumentos é simplesmente retornando uma tupla.

Em Python:

def func(a, b, c):
  return b, c

pode ser reescrito como

def func(my_triple):
  return (my_triple[1], my_triple[2])

para tornar a comparação óbvia.

O primeiro caso é apenas açúcar sintático para o último; ambos recebem um triplo como argumento, mas o primeiro padrão corresponde ao seu argumento para executar a desestruturação automática em seus componentes constituintes. Assim, mesmo linguagens sem correspondência geral de padrões geral admitem alguma forma de correspondência básica de padrões em alguns de seus tipos (o Python admite a correspondência de padrões nos tipos de produto e registro).


Voltando à pergunta em questão: não há uma resposta única para sua pergunta, porque seria como perguntar "qual deve ser o tipo de retorno de uma função arbitrária"? Depende da função e do caso de uso. E, aliás, se os "múltiplos valores de retorno" forem realmente independentes, provavelmente deverão ser calculados por funções separadas.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.