Frage Wie gibst du mehrere Werte in Python zurück?


Die kanonische Methode, mehrere Werte in Sprachen zurückzugeben, die dies unterstützen, ist häufig tupling.

Option: Verwenden eines Tupels

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, da die Anzahl der zurückgegebenen Werte zunimmt. Was, wenn Sie vier oder fünf Werte zurückgeben möchten? Sicher, Sie könnten sie weiter tupfen, aber es wird leicht zu vergessen, welcher Wert wo ist. Es ist auch ziemlich hässlich, sie auszupacken, wo auch immer Sie sie erhalten möchten.

Option: Verwenden eines Wörterbuchs

Der nächste logische Schritt scheint zu sein, eine Art "Notation" einzuführen. In Python ist der offensichtliche Weg, dies zu tun, mittels eines dict.

Folgendes berücksichtigen:

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

(Bearbeiten- Nur um klar zu sein, y0, y1 und y2 sind nur als abstrakte Bezeichner gemeint. Wie bereits erwähnt, würden Sie in der Praxis sinnvolle Bezeichner verwenden)

Jetzt haben wir einen Mechanismus, mit dem wir ein bestimmtes Mitglied des zurückgegebenen Objekts projizieren können. Beispielsweise,

result['y0']

Option: Verwenden einer Klasse

Es gibt jedoch noch eine andere Möglichkeit. Wir könnten stattdessen eine spezialisierte Struktur zurückgeben. Ich habe das im Zusammenhang mit Python dargestellt, aber ich bin mir sicher, dass es auch für andere Sprachen gilt. In der Tat, wenn Sie in C arbeiten, könnte dies Ihre einzige Option sein. Hier geht:

class ReturnValue(object):
  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 vorherigen vielleicht sehr ähnlich in Bezug auf Klempnerarbeit - Immerhin { y0, y1, y2 } nur am Ende Einträge im internen sein __dict__ des ReturnValue.

Es gibt jedoch ein zusätzliches Feature, das von Python für kleine Objekte zur Verfügung gestellt wird __slots__ Attribut. 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

Von dem Python-Referenzhandbuch:

Das __slots__ Die Deklaration nimmt eine Sequenz von Instanzvariablen und reserviert gerade genug Speicherplatz in jeder Instanz, um einen Wert für jede Variable zu speichern. Raum wird gespeichert, weil __dict__ wird nicht für jede Instanz erstellt.

Option: Verwenden einer Liste

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 jedoch meine am wenigsten bevorzugte Methode. Ich nehme an, ich bin von Haskell betroffen, aber die Idee gemischter Listen war mir immer unangenehm. In diesem speziellen Beispiel ist die Liste kein gemischter Typ, könnte aber denkbar sein. Eine Liste, die auf diese Art und Weise verwendet wird, gewinnt wirklich nichts in Bezug auf das Tupel, soweit ich das beurteilen kann. Der einzige wirkliche Unterschied zwischen Listen und Tupeln in Python ist, dass es Listen gibt veränderlich, Tupel sind es nicht. Ich persönlich tendiere dazu, die Konventionen aus der funktionalen Programmierung zu übernehmen: benutze Listen für eine beliebige Anzahl von Elementen des gleichen Typs und Tupel für eine feste Anzahl von Elementen vorbestimmter Typen.

Frage

Nach der langen Präambel kommt die unvermeidliche Frage. Welche Methode (meinst du) ist am besten?

In der Regel habe ich die Wörterbuchroute gefunden, weil es weniger Setup-Arbeit erfordert. Aus Sicht der Typen ist es jedoch besser, die Klassenroute zu verwenden, da Sie dadurch möglicherweise nicht verwirren können, was ein Wörterbuch darstellt. Auf der anderen Seite gibt es einige in der Python-Community, die sich fühlen implizierte Schnittstellen sollten gegenüber expliziten Schnittstellen bevorzugt werdenAn diesem Punkt ist der Typ des Objekts wirklich nicht relevant, da Sie sich grundsätzlich auf die Konvention verlassen, dass das gleiche Attribut immer die gleiche Bedeutung hat.

Wie können Sie also in Python mehrere Werte zurückgeben?


804
2017-12-10 01:55


Ursprung


Antworten:


Benannte Tupel wurden zu diesem Zweck in 2.6 hinzugefügt. Siehe auch os.stat für ein ähnliches eingebautes Beispiel.

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

511
2017-12-10 16:36



Bei kleinen Projekten finde ich es am einfachsten mit Tupeln zu arbeiten. Wenn das zu schwierig wird (und nicht vorher), fange ich an, Dinge in logische Strukturen zu gruppieren, aber ich denke, Ihre vorgeschlagene Verwendung von Wörterbüchern und ReturnValue-Objekten ist falsch (oder zu einfach).

Das Zurückgeben eines Wörterbuchs mit den Tasten y0, y1, y2 usw. bietet keinen Vorteil gegenüber Tupeln. Die Rückgabe einer ReturnValue-Instanz mit den Eigenschaften .y0 .y1 .y2 usw. bietet auch keinen Vorteil gegenüber Tupeln. Sie müssen anfangen, Dinge zu benennen, wenn Sie irgendwo hinkommen wollen, und Sie können das trotzdem mit Tupeln tun:

def getImageData(filename):
  [snip]
  return size, (format, version, compression), (width,height)
size, type, dimensions = getImageData(x)

Meiner Meinung nach ist die einzige gute Technik jenseits von Tupeln, echte Objekte mit geeigneten Methoden und Eigenschaften zurückzugeben, so wie Sie es erhalten re.match() oder open(file).


164
2017-12-10 02:22



Viele der Antworten legen nahe, dass Sie eine Sammlung einer Art, wie ein Wörterbuch oder eine Liste, zurückgeben müssen. Sie könnten die zusätzliche Syntax weglassen und einfach die Rückgabewerte komma-separiert ausgeben. Hinweis: Dies gibt technisch ein Tupel zurück.

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

gibt:

True
False

122
2018-04-14 20:08



Ich stimme für das Wörterbuch.

Ich finde, wenn ich eine Funktion mache, die mehr als 2-3 Variablen zurückgibt, falte ich sie in einem Wörterbuch zusammen. Ansonsten vergesse ich die Reihenfolge und den Inhalt dessen, was ich zurückgebe.

Außerdem macht die Einführung einer "speziellen" Struktur den Code schwieriger zu befolgen. (Jemand anderes muss den Code durchsuchen, um herauszufinden, was es ist)

Wenn Sie sich Gedanken über den Typ machen, verwenden Sie beschreibende Wörterbuchschlüssel, z. B. "x-values ​​list".

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

53
2017-12-10 02:42



Eine andere Option wäre die Verwendung von Generatoren:

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

Obwohl IMHO-Tupel normalerweise am besten sind, außer in Fällen, in denen die zurückgegebenen Werte Kandidaten für die Kapselung in einer Klasse sind.


31
2018-02-23 15:26



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

22
2018-01-21 20:57



Ich bevorzuge Tupel, wenn ein Tupel sich "natürlich" fühlt; Koordinaten sind ein typisches Beispiel, wo die getrennten Objekte eigenständig stehen können, z. in nur einer Achse Skalierungsberechnungen, und die Reihenfolge ist wichtig. Hinweis: Wenn ich die Elemente sortieren oder mischen kann, ohne die Bedeutung der Gruppe zu beeinträchtigen, sollte ich wahrscheinlich kein Tupel verwenden.

Ich verwende Wörterbücher nur dann als Rückgabewert, wenn die gruppierten Objekte nicht immer gleich sind. Denken Sie an optionale E-Mail-Header.

Für den Rest der Fälle, wo die gruppierten Objekte innerhalb der Gruppe eine inhärente Bedeutung haben oder ein vollwertiges Objekt mit eigenen Methoden benötigt wird, verwende ich eine Klasse.


22
2017-12-10 02:40



ich bevorzuge

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

Es scheint, alles andere ist nur zusätzlicher Code, um das Gleiche zu tun.


20
2017-12-10 02:00



+1 auf S.Lotts Vorschlag einer benannten Container-Klasse.

Für Python 2.6 und höher, a benanntes Tupel bietet eine nützliche Möglichkeit zum einfachen Erstellen dieser Container-Klassen und die Ergebnisse sind "leichtgewichtig und benötigen nicht mehr Speicher als normale Tupel".


13
2017-12-10 03:51



Pythons Tupel, Dicts und Objekte bieten dem Programmierer einen reibungslosen Kompromiss zwischen Formalität und Zweckmäßigkeit für kleine Datenstrukturen ("Dinge"). Für mich ist die Entscheidung, wie man etwas repräsentiert, hauptsächlich davon abhängig, wie ich die Struktur nutzen werde. In C ++ ist es eine gängige Konvention struct für reine Datenelemente und class für Objekte mit Methoden, obwohl Sie legal Methoden auf eine setzen können struct; meine Gewohnheit ist in Python ähnlich, mit dict und tuple anstelle von struct.

Für Koordinatensätze verwende ich a tuple eher als ein Punkt class oder ein dict (und beachten Sie, dass Sie a tuple als ein Wörterbuchschlüssel, so dicts machen große spärliche multidimensionale Arrays).

Wenn ich über eine Liste von Dingen iteriere, bevorzuge ich das Auspacken tuples auf der Iteration:

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

... da die Objektversion mehr unübersichtlich zu lesen ist:

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

... geschweige denn die dict.

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

Wenn das Ding weit verbreitet ist und Sie ähnliche nicht-triviale Operationen an mehreren Stellen im Code ausführen, lohnt es sich normalerweise, es mit geeigneten Methoden zu einem Klassenobjekt zu machen.

Schließlich, wenn ich Daten mit Nicht-Python-Systemkomponenten austauschen werde, werde ich sie am häufigsten in einem dict weil das am besten für die JSON-Serialisierung geeignet ist.


13
2017-08-13 20:18



Im Allgemeinen ist die "spezialisierte Struktur" tatsächlich ein vernünftiger 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 suche möglichst nach Namen für anonyme Strukturen. Sinnvolle Namen machen die Dinge klarer.


12
2017-12-10 02:15