Die kanonische Methode zur Rückgabe mehrerer Werte in Sprachen, die dies unterstützen, ist häufig tupling.
Betrachten Sie dieses triviale Beispiel:
def f(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return (y0, y1, y2)
Dies wird jedoch schnell problematisch, wenn die Anzahl der zurückgegebenen Werte steigt. Was ist, wenn Sie vier oder fünf Werte zurückgeben wollen? Sicher, man könnte sie weiter tupeln, aber dann vergisst man leicht, welcher Wert wo ist. Außerdem ist es ziemlich hässlich, sie auszupacken, wo immer man sie erhalten möchte.
Der nächste logische Schritt scheint zu sein, eine Art 'Datensatz-Notation' einzuführen. In Python ist die offensichtliche Art, dies zu tun, die Verwendung eines Dict
.
Betrachten Sie das Folgende:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0': y0, 'y1': y1 ,'y2': y2}
(Nur um das klarzustellen: y0, y1 und y2 sind nur als abstrakte Bezeichner gedacht. Wie bereits erwähnt, würden Sie in der Praxis sinnvolle Bezeichner verwenden).
Jetzt haben wir einen Mechanismus, mit dem wir ein bestimmtes Element des zurückgegebenen Objekts herausprojizieren können. Zum Beispiel,
result['y0']
Es gibt jedoch noch eine andere Möglichkeit. Wir könnten stattdessen eine spezialisierte Struktur zurückgeben. Ich habe dies im Kontext von Python beschrieben, aber ich bin sicher, dass es auch für andere Sprachen gilt. Wenn Sie in C arbeiten, könnte dies sogar Ihre einzige Möglichkeit sein. So geht's:
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 sind die beiden vorangegangenen vielleicht sehr ähnlich - schließlich sind { y0, y1, y2 }
am Ende nur Einträge im internen __dict__
des ReturnValue
.
Es gibt jedoch eine zusätzliche Funktion, die Python für winzige Objekte bereitstellt, das Attribut __slots__
. Die Klasse könnte ausgedrückt werden als:
class ReturnValue(object):
__slots__ = ["y0", "y1", "y2"]
def __init__(self, y0, y1, y2):
self.y0 = y0
self.y1 = y1
self.y2 = y2
Aus dem Python Reference Manual:
Die
__slots__
Deklaration nimmt eine Sequenz von Instanzvariablen und reserviert gerade genug Platz in jeder Instanz, um einen Wert für jede Variable zu halten. Platz wird gespart, weil__dict__
nicht für jede Instanz erstellt wird.
Verwenden Sie die neuen Datenklassen von Python 3.7 und geben Sie eine Klasse mit automatisch hinzugefügten speziellen Methoden, Typisierung und anderen nützlichen Werkzeugen zurück:
@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)
Ein weiterer Vorschlag, den ich übersehen hatte, stammt von Bill the Lizard:
def h(x):
result = [x + 1]
result.append(x * 3)
result.append(y0 ** y3)
return result
Dies ist allerdings meine unbeliebteste Methode. Ich nehme an, dass ich durch die Erfahrung mit Haskell verdorben bin, aber die Idee von Listen gemischten Typs hat sich für mich immer unangenehm angefühlt. In diesem speziellen Beispiel ist die Liste -nicht- vom gemischten Typ, aber es wäre denkbar, dass sie es ist.
Eine Liste, die auf diese Weise verwendet wird, bringt im Vergleich zum Tupel keinen wirklichen Vorteil, soweit ich das beurteilen kann. Der einzige wirkliche Unterschied zwischen Listen und Tupeln in Python ist, dass Listen veränderbar sind, Tupel hingegen nicht.
Ich persönlich neige dazu, die Konventionen aus der funktionalen Programmierung zu übernehmen: Listen für eine beliebige Anzahl von Elementen desselben Typs und Tupel für eine feste Anzahl von Elementen eines bestimmten Typs.
Nach der langen Präambel kommt die unvermeidliche Frage. Welche Methode ist (Ihrer Meinung nach) die beste?
Ich habe mich in der Regel für die Wörterbuchmethode entschieden, weil sie weniger Einrichtungsarbeit erfordert. Aus der Sicht des Typs ist es aber vielleicht besser, den Weg über die Klasse zu gehen, da man dann nicht so leicht verwechseln kann, was ein Wörterbuch darstellt.
Andererseits gibt es in der Python-Gemeinschaft einige, die der Meinung sind, dass implizite Schnittstellen expliziten Schnittstellen vorzuziehen sind, wobei der Typ des Objekts eigentlich nicht relevant ist, da man sich im Grunde auf die Konvention verlässt, dass das gleiche Attribut immer die gleiche Bedeutung hat.
Wie kann man also in Python mehrere Werte zurückgeben?
Ich bevorzuge:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
Alles andere scheint nur zusätzlicher Code zu sein, um das Gleiche zu tun.
Im Allgemeinen ist die "spezialisierte Struktur" tatsächlich ein sinnvoller aktueller Zustand eines Objekts, mit eigenen Methoden.
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
Ich mag es, Namen für anonyme Strukturen zu finden, wo es möglich ist. Aussagekräftige Namen machen die Dinge klarer.
In Sprachen wie Python würde ich in der Regel ein Wörterbuch verwenden, da dies weniger Aufwand bedeutet als die Erstellung einer neuen Klasse.
Wenn ich jedoch immer wieder denselben Satz von Variablen zurückgeben muss, dann ist wahrscheinlich eine neue Klasse erforderlich, die ich ausklammern werde.