Come posso restituire più valori da una funzione? [chiuso]


1067

Il modo canonico di restituire più valori nelle lingue che lo supportano è spesso tupling .

Opzione: usare una tupla

Considera questo banale esempio:

def f(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return (y0, y1, y2)

Tuttavia, questo diventa rapidamente problematico all'aumentare del numero di valori restituiti. Cosa succede se si desidera restituire quattro o cinque valori? Certo, potresti continuare a agitarli, ma diventa facile dimenticare quale valore è dove. È anche piuttosto brutto disimballarli ovunque tu voglia riceverli.

Opzione: utilizzo di un dizionario

Il prossimo passo logico sembra essere quello di introdurre una sorta di "notazione record". In Python, il modo più ovvio per farlo è tramite a dict.

Considera quanto segue:

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

(Giusto per essere chiari, y0, y1 e y2 sono intesi solo come identificatori astratti. Come sottolineato, in pratica useresti identificatori significativi.)

Ora, abbiamo un meccanismo per cui possiamo proiettare un particolare membro dell'oggetto restituito. Per esempio,

result['y0']

Opzione: utilizzo di una classe

Tuttavia, c'è un'altra opzione. Potremmo invece restituire una struttura specializzata. L'ho inquadrato nel contesto di Python, ma sono sicuro che si applica anche ad altre lingue. In effetti, se lavorassi in C, questa potrebbe benissimo essere la tua unica opzione. Ecco qui:

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)

In Python i due precedenti sono forse molto simili in termini di impianto idraulico - dopo tutto { y0, y1, y2 }finiscono per essere voci all'interno __dict__del ReturnValue.

C'è una funzionalità aggiuntiva fornita da Python anche se per piccoli oggetti, l' __slots__attributo. La classe potrebbe essere espressa come:

class ReturnValue(object):
  __slots__ = ["y0", "y1", "y2"]
  def __init__(self, y0, y1, y2):
     self.y0 = y0
     self.y1 = y1
     self.y2 = y2

Dal manuale di riferimento di Python :

La __slots__dichiarazione accetta una sequenza di variabili di istanza e riserva uno spazio sufficiente in ciascuna istanza per contenere un valore per ogni variabile. Lo spazio viene salvato perché __dict__non viene creato per ogni istanza.

Opzione: utilizzo di una dataclasse (Python 3.7+)

Utilizzando i nuovi dataclass di Python 3.7, restituisci una classe con metodi speciali aggiunti automaticamente, digitazione e altri strumenti utili:

@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)

Opzione: utilizzare un elenco

Un altro suggerimento che avevo trascurato viene da Bill the Lizard:

def h(x):
  result = [x + 1]
  result.append(x * 3)
  result.append(y0 ** y3)
  return result

Questo è il mio metodo meno preferito però. Suppongo di essere contaminato dall'esposizione a Haskell, ma l'idea di elenchi di tipo misto mi è sempre sembrata a disagio. In questo esempio particolare l'elenco è di tipo non misto, ma è possibile che lo sia.

Un elenco usato in questo modo in realtà non guadagna nulla rispetto alla tupla, per quanto posso dire. L'unica vera differenza tra liste e tuple in Python è che le liste sono mutabili , mentre le tuple no.

Personalmente tendo a riproporre le convenzioni dalla programmazione funzionale: uso liste per qualsiasi numero di elementi dello stesso tipo e tuple per un numero fisso di elementi di tipi predeterminati.

Domanda

Dopo il lungo preambolo, arriva l'inevitabile domanda. Quale metodo (pensi) sia il migliore?


10
In esempi eccellenti usi variabile y3, ma se y3 non viene dichiarato globale, questo potrebbe NameError: global name 'y3' is not definedforse usare 3?
hetepeperfan,

11
Molte grandi domande con grandi risposte vengono chiuse perché sorge la parola chiave "opinione". Si potrebbe sostenere che l'intero SO si basa sull'opinione, ma è un'opinione informata da fatti, riferimenti e competenze specifiche. Solo perché qualcuno chiede "quale pensi sia il migliore" non significa che stanno chiedendo opinioni personali sottratte a fatti, riferimenti e competenze specifiche del mondo reale. Quasi certamente chiedono esattamente quel tipo di opinione, il tipo basato accuratamente e documentato con i fatti, i riferimenti e le competenze specifiche che la persona ha usato per formare l'opinione.
NeilG

@hetepeperfan non ha bisogno di cambiare 3, e nemmeno definire y3 in globale, potresti anche usare un nome locale y3, che farà lo stesso lavoro.
Okie,

Risposte:


637

Le tuple nominate sono state aggiunte in 2.6 per questo scopo. Vedi anche os.stat per un simile esempio incorporato.

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

Nelle recenti versioni di Python 3 (3.6+, credo), la nuova typinglibreria ha ottenuto la NamedTupleclasse per rendere le tuple nominate più facili da creare e più potenti. L'ereditarietà da typing.NamedTupleconsente di utilizzare docstring, valori predefiniti e digitare annotazioni.

Esempio (dai documenti):

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

employee = Employee('Guido')
assert employee.id == 3

68
Questa è la risposta corretta solo perché è l'unica struttura canonica che l'OP non ha preso in considerazione e perché affronta il suo problema di gestire lunghe tuple. Dovrebbe essere contrassegnato come accettato.
attacco aereo

7
Bene, la logica progettuale per namedtupleavere un footprint di memoria più piccolo per i risultati di massa (lunghi elenchi di tuple, come i risultati delle query DB). Per i singoli elementi (se la funzione in questione non viene chiamata spesso), anche i dizionari e le classi vanno bene. Ma anche in questo caso i nametuples sono una soluzione piacevole / piacevole.
Lutz Prechelt,

8
@wom: non farlo. Python non fa alcuno sforzo per unificare le namedtupledefinizioni (ogni chiamata ne crea una nuova), la creazione della namedtupleclasse è relativamente costosa sia nella CPU che nella memoria e tutte le definizioni della classe implicano intrinsecamente riferimenti ciclici (quindi su CPython, stai aspettando un'esecuzione ciclica del GC per essere rilasciati). Inoltre rende impossibile per picklela classe (e quindi, impossibile utilizzare istanze con multiprocessingnella maggior parte dei casi). Ogni creazione della classe sul mio 3,6,4 x64 consuma ~ 0,337 ms e occupa poco meno di 1 KB di memoria, eliminando qualsiasi risparmio di istanza.
ShadowRanger

3
Noterò che Python 3.7 ha migliorato la velocità di creazione di nuove namedtupleclassi ; i costi della CPU scendono di circa un fattore 4x , ma sono ancora circa 1000 volte più alti del costo per creare un'istanza e il costo della memoria per ciascuna classe rimane elevato (ho sbagliato nel mio ultimo commento su "meno di 1 KB" per la classe, di _sourceper sé è in genere 1,5 KB; _sourceviene rimosso in 3.7, quindi è probabilmente più vicino all'affermazione originale di poco meno di 1 KB per creazione della classe).
ShadowRanger

4
@SergeStroobandt - Fa parte della libreria standard, non solo un built-in. Non devi preoccuparti che potrebbe non essere installato su un altro sistema con Python> = 2.6. O ti opponi semplicemente alla riga di codice aggiuntiva?
Giustino,

234

Per i piccoli progetti trovo più semplice lavorare con le tuple. Quando diventa troppo difficile da gestire (e non prima) inizio a raggruppare le cose in strutture logiche, tuttavia penso che l'uso suggerito di dizionari e ReturnValueoggetti sia sbagliato (o troppo semplicistico).

Tornando un dizionario con i tasti "y0", "y1", "y2", ecc non offre alcun vantaggio rispetto tuple. Tornando un ReturnValueesempio con proprietà .y0, .y1, .y2, ecc non offre alcun vantaggio rispetto tuple sia. Devi iniziare a nominare le cose se vuoi arrivare ovunque, e puoi farlo usando comunque le tuple:

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

size, type, dimensions = get_image_data(x)

IMHO, l'unica buona tecnica al di là delle tuple è quella di restituire oggetti reali con metodi e proprietà adeguati, come si ottiene da re.match()o open(file).


6
Domanda: c'è qualche differenza tra size, type, dimensions = getImageData(x)e (size, type, dimensions) = getImageData(x)? Vale a dire, avvolgere il lato sinistro di un compito tupled fa la differenza?
Reb.Cabin

11
@ Reb.Cabin Non c'è differenza. La tupla è identificata da una virgola e l'uso delle parentesi è solo per raggruppare le cose. Ad esempio (1)è un int mentre (1,)o 1,è una tupla.
Phil

19
Ri "restituire un dizionario con le chiavi y0, y1, y2 ecc. Non offre alcun vantaggio rispetto alle tuple": il dizionario ha il vantaggio di poter aggiungere campi al dizionario restituito senza rompere il codice esistente.
ostrokach,

Ri "restituire un dizionario con le chiavi y0, y1, y2 ecc. Non offre alcun vantaggio rispetto alle tuple": è anche più leggibile e meno soggetto a errori quando si accede ai dati in base al suo nome piuttosto che alla posizione.
Denis Dollfus,

204

Molte risposte suggeriscono che è necessario restituire una raccolta di qualche tipo, come un dizionario o un elenco. È possibile tralasciare la sintassi aggiuntiva e scrivere semplicemente i valori restituiti, separati da virgola. Nota: questo tecnicamente restituisce una tupla.

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

dà:

True
False

24
Stai ancora restituendo una raccolta. È una tupla. Preferisco la parentesi per renderla più esplicita. Prova questo: type(f())ritorna <class 'tuple'>.
Igor,

20
@Igor: non vi è alcun motivo per rendere tupleesplicito l' aspetto; non è molto importante che tu stia restituendo un tuple, questo è il linguaggio per restituire un periodo di più valori. Stesso motivo per cui ometti i genitori con il linguaggio di scambio x, y = y, x, l'inizializzazione multipla x, y = 0, 1, ecc .; certo, rende le cose tuplesotto il cofano, ma non c'è motivo di renderle esplicite, dal momento che le tuples non sono affatto il punto. Il tutorial di Python introduce assegnazioni multiple molto prima ancora che tocchi tuples.
ShadowRanger

@ShadowRanger qualsiasi sequenza di valori separati da una virgola nella parte destra di =è una tupla in Python con o senza parentesi. Quindi non c'è in realtà esplicito o implicito qui. a, b, c è una tupla tanto quanto (a, b, c). Inoltre, quando si restituiscono tali valori non è possibile creare la tupla "sotto il cofano", perché è solo una semplice tupla. L'OP ha già menzionato le tuple, quindi in realtà non c'è alcuna differenza tra ciò che ha menzionato e ciò che mostra questa risposta. Nessuno
Ken4scholars il

2
Questa è letteralmente la prima opzione suggerita nella domanda
endolito il

1
@endolith I due volte il ragazzo fa una domanda ( "Come faccio a restituire più valori?" e "Come si restituire più valori?") sono risposta da questa risposta. Il testo della domanda è cambiato a volte. Ed è una domanda basata sull'opinione.
Joseph Hansen,

74

Voto per il dizionario.

Trovo che se creo una funzione che restituisce qualcosa di più di 2-3 variabili li piegherò in un dizionario. Altrimenti tendo a dimenticare l'ordine e il contenuto di ciò che sto tornando.

Inoltre, l'introduzione di una struttura "speciale" rende il codice più difficile da seguire. (Qualcun altro dovrà cercare nel codice per scoprire di cosa si tratta)

Se sei preoccupato per la ricerca del tipo, utilizza le chiavi del dizionario descrittivo, ad esempio "elenco valori x".

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

5
dopo molti anni di programmazione, tendo a qualsiasi cosa sia richiesta la struttura dei dati e delle funzioni. Prima di tutto, puoi sempre refactoring secondo necessità.
monkut,

Come otterremmo i valori all'interno del dizionario senza chiamare la funzione più volte? Ad esempio, se voglio usare y1 e y3 in un'altra funzione?
Matt,

3
assegnare i risultati a una variabile separata. result = g(x); other_function(result)
monkut

1
@monkut sì. In questo modo consente anche di passare il risultato a diverse funzioni, che prendono argomenti diversi dal risultato, senza dover fare riferimento in modo specifico a parti specifiche del risultato ogni volta.
Gnudiff

38

Un'altra opzione sarebbe usare i generatori:

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

Sebbene le tuple IMHO siano in genere le migliori, tranne nei casi in cui i valori restituiti sono candidati per l'incapsulamento in una classe.


1
Questa sembra essere la soluzione più pulita e ha una sintassi pulita. Ci sono degli svantaggi in questo? Se non usi tutti i rendimenti, ci sono rendimenti "non spesi" in attesa di farti del male?
Jiminion,

24
Questo può essere "pulito", ma non sembra affatto intuitivo. Come potrebbe qualcuno che non abbia mai incontrato questo schema prima di sapere che eseguire il disimballaggio automatico della tupla li innescerebbe yield?
coredumperror,

1
@CoreDumpError, i generatori sono proprio ... generatori. Non vi è alcuna differenza esterna tra def f(x): …; yield b; yield a; yield rvs. (g for g in [b, a, r]), ed entrambi si convertiranno prontamente in elenchi o tuple e come tali supporteranno il disimballaggio della tupla. La forma del generatore di tupla segue un approccio funzionale, mentre la forma della funzione è indispensabile e consentirà il controllo del flusso e l'assegnazione variabile.
slitta il

30

Preferisco usare le tuple ogni volta che una tupla si sente "naturale"; le coordinate sono un tipico esempio, in cui gli oggetti separati possono stare da soli, ad esempio nei calcoli di ridimensionamento su un solo asse e l'ordine è importante. Nota: se riesco a ordinare o mescolare gli elementi senza effetti negativi sul significato del gruppo, probabilmente non dovrei usare una tupla.

Uso i dizionari come valore di ritorno solo quando gli oggetti raggruppati non sono sempre gli stessi. Pensa alle intestazioni e-mail opzionali.

Per il resto dei casi, in cui gli oggetti raggruppati hanno un significato intrinseco all'interno del gruppo o è necessario un oggetto completo con i propri metodi, uso una classe.


29

Preferisco:

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

Sembra che tutto il resto sia solo un codice aggiuntivo per fare la stessa cosa.


22
Le tuple sono più facili da decomprimere: y0, y1, y2 = g () con un dict che devi fare: result = g () y0, y1, y2 = result.get ('y0'), result.get ('y1' ), result.get ('y2') che è un po 'brutto. Ogni soluzione ha i suoi "vantaggi" e i suoi "svantaggi".
Oli,

27
>>> def func():
...    return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3

@edouard No, non restituisce una tupla, non un elenco.
Simon Hibbs,

1
la destrutturazione è l' argomento per la restituzione delle liste secondo me
semiomante

21

Generalmente, la "struttura specializzata" È in realtà uno stato corrente sensibile di un oggetto, con i suoi metodi.

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

Mi piace trovare nomi per strutture anonime, ove possibile. I nomi significativi rendono le cose più chiare.


20

Le tuple, i dadi e gli oggetti di Python offrono al programmatore un compromesso regolare tra formalità e praticità per le piccole strutture di dati ("cose"). Per me, la scelta di come rappresentare una cosa è dettata principalmente da come userò la struttura. In C ++, è una convenzione comune da utilizzare structper gli elementi solo dati e classper gli oggetti con metodi, anche se è possibile legalmente mettere metodi su un struct; la mia abitudine è simile in Python, con dicte tupleal posto distruct .

Per i set di coordinate, userò un tupleanziché un punto classo un dict(e noterò che puoi usare un tuplecome chiave del dizionario, quindidict fai grandi matrici multidimensionali sparse).

Se sto andando a scorrere su un elenco di cose, preferisco decomprimere tuplei messaggi sull'iterazione:

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

... poiché la versione dell'oggetto è più ingombra da leggere:

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

... figuriamoci il dict.

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

Se la cosa è ampiamente usata e ti ritrovi a fare operazioni simili non banali su di essa in più punti del codice, di solito vale la pena renderlo un oggetto di classe con metodi appropriati.

Infine, se avrò intenzione di scambiare dati con componenti di sistema non Python, li terrò spesso nel modo dictpiù adatto alla serializzazione JSON.


19

+1 su suggerimento di S.Lott di una classe contenitore denominata.

Per Python 2.6 e versioni successive, una tupla con nome fornisce un modo utile per creare facilmente queste classi di contenitori, ei risultati sono "leggeri e non richiedono più memoria delle tuple normali".


4

In lingue come Python, di solito userei un dizionario in quanto comporta meno spese generali rispetto alla creazione di una nuova classe.

Tuttavia, se mi ritrovo a restituire costantemente lo stesso insieme di variabili, ciò probabilmente implica una nuova classe che prenderò in considerazione.


4

Vorrei usare un dict per passare e restituire i valori da una funzione:

Usa il modulo variabile come definito nel modulo .

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}

Questo è il modo più efficiente per me e per l'unità di elaborazione.

Devi passare solo un puntatore e restituire solo un puntatore.

Non è necessario modificare gli argomenti delle funzioni (migliaia di essi) ogni volta che si modifica il codice.


I dicts sono mutabili. Se si passa un dict a una funzione e tale funzione modifica il dict, le modifiche verranno applicate al di fuori dell'ambito di tale funzione. Avere la funzione restituire il dict alla fine potrebbe implicare che la funzione non abbia effetti collaterali, quindi il valore non deve essere restituito, chiarendo che testmodificherà direttamente il valore. Confronta questo con dict.update, che non restituisce un valore.
slitta il

@sleblanc "Avere la funzione restituire il dict alla fine potrebbe implicare che la funzione non abbia effetti collaterali". Ciò non implica che, come hai detto, dict sia mutevole. Tuttavia, la restituzione formnon danneggia la leggibilità né le prestazioni. Nei casi in cui potrebbe essere necessario riformattare form, restituendolo [modulo] si assicura che formvenga restituito l'ultimo perché non si terrà traccia delle modifiche al modulo da nessuna parte.
Elis Byberi,

3

"Best" è una decisione parzialmente soggettiva. Utilizzare le tuple per piccoli set di ritorno nel caso generale in cui un valore immutabile è accettabile. Una tupla è sempre preferibile a un elenco quando la mutabilità non è un requisito.

Per valori di ritorno più complessi, o nel caso in cui la formalità è preziosa (ovvero codice di valore elevato), una tupla con nome è migliore. Nel caso più complesso di solito un oggetto è il migliore. Tuttavia, è davvero la situazione che conta. Se ha senso restituire un oggetto perché è quello che hai naturalmente alla fine della funzione (es. Modello Factory), allora restituisci l'oggetto.

Come disse il saggio:

L'ottimizzazione prematura è la radice di tutti i mali (o almeno la maggior parte di essi) nella programmazione.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.