Il modo canonico per restituire valori multipli nei linguaggi che lo supportano è spesso tupling.
Opzione ###: Usare una tupla Considerate questo esempio banale:
def f(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return (y0, y1, y2)
Tuttavia, questo diventa rapidamente problematico quando il numero di valori restituiti aumenta. Cosa succede se volete restituire quattro o cinque valori? Certo, potreste continuare a tuplarli, ma diventa facile dimenticare quale valore è dove. È anche piuttosto brutto spacchettarli dove si vuole riceverli.
Opzione ###: Usare un dizionario
Il prossimo passo logico sembra essere quello di introdurre una sorta di 'notazione di record'. In Python, il modo ovvio per farlo è per mezzo di un dict
.
Considerate 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 solo intesi come identificatori astratti. Come sottolineato, in pratica si usano identificatori significativi).
Ora, abbiamo un meccanismo con cui possiamo proiettare un particolare membro dell'oggetto restituito. Per esempio,
result['y0']
Tuttavia, c'è un'altra opzione. Potremmo invece restituire una struttura specializzata. Ho inquadrato questo nel contesto di Python, ma sono sicuro che si applica anche ad altri linguaggi. Infatti, se steste lavorando in C, questa potrebbe benissimo essere la vostra 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 idraulica - dopo tutto { y0, y1, y2 }
finiscono per essere voci nel __dict__
interno del ReturnValue
.
C'è però una caratteristica aggiuntiva fornita da Python per i piccoli oggetti, l'attributo __slots__
. 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 dichiarazione __slots__
prende una sequenza di variabili di istanza e riserva lo spazio sufficiente in ogni istanza per contenere un valore per ogni variabile. Lo spazio viene risparmiato perché __dict__
non viene creato per ogni istanza.
Usando le nuove dataclass di Python 3.7, restituisci una classe con metodi speciali aggiunti automaticamente, tipizzazione 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)
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. Suppongo di essere stato contaminato dall'esposizione ad Haskell, ma l'idea di liste di tipo misto mi ha sempre messo a disagio. In questo particolare esempio la lista non è di tipo misto, ma potrebbe plausibilmente esserlo.
Una lista usata in questo modo non guadagna davvero 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 portare avanti le convenzioni della programmazione funzionale: usare liste per qualsiasi numero di elementi dello stesso tipo, e tuple per un numero fisso di elementi di tipi predeterminati.
Dopo il lungo preambolo, arriva l'inevitabile domanda. Quale metodo (pensi) sia migliore?
In genere mi sono trovato a seguire la strada del dizionario perché comporta meno lavoro di configurazione. Da un punto di vista tipologico, tuttavia, si potrebbe essere meglio andare sulla strada della classe, dal momento che questo può aiutare a evitare di confondere ciò che un dizionario rappresenta.
D'altra parte, ci sono alcuni nella comunità Python che sentono che le interfacce implicite dovrebbero essere preferite alle interfacce esplicite, a quel punto il tipo dell'oggetto non è davvero rilevante, dato che fondamentalmente ci si basa sulla convenzione che lo stesso attributo avrà sempre lo stesso significato.
Quindi, come si fa a restituire valori multipli in Python?
In generale, la "struttura specializzata" è in realtà uno stato corrente sensibile di un oggetto, con i suoi propri 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 le strutture anonime dove possibile. Nomi significativi rendono le cose più chiare.
In linguaggi come Python, di solito uso un dizionario perché comporta meno spese generali che creare una nuova classe.
Tuttavia, se mi ritrovo a restituire costantemente lo stesso set di variabili, allora questo probabilmente implica una nuova classe che io non creerò.