Ist es pythonisch für eine Funktion, mehrere Werte zurückzugeben?

81

In Python kann eine Funktion mehrere Werte zurückgeben. Hier ist ein erfundenes Beispiel:

def divide(x, y):
    quotient = x/y
    remainder = x % y
    return quotient, remainder  

(q, r) = divide(22, 7)

Dies scheint sehr nützlich zu sein, aber es sieht so aus, als könnte es auch missbraucht werden ("Nun ... Funktion X berechnet bereits, was wir als Zwischenwert benötigen. Lassen wir X diesen Wert auch zurückgeben").

Wann sollten Sie die Linie ziehen und eine andere Methode definieren?

Schreibgeschützt
quelle

Antworten:

107

Absolut (für das von Ihnen angegebene Beispiel).

Tupel sind erstklassige Bürger in Python

Es gibt eine eingebaute Funktion divmod(), die genau das tut.

q, r = divmod(x, y) # ((x - x%y)/y, x%y) Invariant: div*y + mod == x

Es gibt andere Beispiele: zip, enumerate, dict.items.

for i, e in enumerate([1, 3, 3]):
    print "index=%d, element=%s" % (i, e)

# reverse keys and values in a dictionary
d = dict((v, k) for k, v in adict.items()) # or 
d = dict(zip(adict.values(), adict.keys()))

Übrigens sind Klammern meistens nicht erforderlich. Zitat aus der Python-Bibliothek Referenz :

Tupel können auf verschiedene Arten konstruiert werden:

  • Verwenden Sie ein Paar Klammern, um das leere Tupel zu kennzeichnen: ()
  • Verwenden eines nachgestellten Kommas für ein Singleton-Tupel: a oder (a,)
  • Trennen von Elementen durch Kommas: a, b, c oder (a, b, c)
  • Verwenden des integrierten tuple (): tuple () oder tuple (iterable)

Funktionen sollten einem einzigen Zweck dienen

Daher sollten sie ein einzelnes Objekt zurückgeben. In Ihrem Fall ist dieses Objekt ein Tupel. Betrachten Sie Tupel als eine zusammengesetzte Ad-hoc-Datenstruktur. Es gibt Sprachen, in denen fast jede einzelne Funktion mehrere Werte zurückgibt (Liste in Lisp).

Manchmal reicht es aus, (x, y)statt zurückzukehren Point(x, y).

Benannte Tupel

Mit der Einführung benannter Tupel in Python 2.6 ist es in vielen Fällen vorzuziehen, benannte Tupel anstelle von einfachen Tupeln zurückzugeben.

>>> import collections
>>> Point = collections.namedtuple('Point', 'x y')
>>> x, y = Point(0, 1)
>>> p = Point(x, y)
>>> x, y, p
(0, 1, Point(x=0, y=1))
>>> p.x, p.y, p[0], p[1]
(0, 1, 0, 1)
>>> for i in p:
...   print(i)
...
0
1
jfs
quelle
1
Die Möglichkeit, solche Tupel zu verwenden, ist sehr praktisch. Ich vermisse diese Fähigkeit manchmal wirklich in Java.
MBCook
3
Vielen Dank, dass Sie Named Tuples erwähnt haben. Ich habe nach so etwas gesucht.
Objekt
Bei begrenzten Rückgabewerten ist dict () möglicherweise einfacher / schneller (insbesondere, wenn die Funktion dann für die REST-API und JSON verwendet wird). Auf meinem System benötigt def test_nt (): Point = namedtuple ('Point', ['x', 'y']) p = Point (10.5, 11.5) json.dumps (p._asdict ()) benötigt 819 μs / Schleife , def test_dict (): d = {'x': 10.5, 'y': 11.5} json.dumps (d) benötigt 15.9μs / loop .... also> 50x schneller
comte
@comte: Ja, Point(10.5, 11.5)ist 6 mal langsamer als {'x': 10.5, 'y':11.5}. Die absoluten Zeiten sind 635 ns +- 26 nsgegen105 ns +- 4 ns . Es ist unwahrscheinlich, dass beides einen Engpass in der Anwendung darstellt. Optimieren Sie nicht, es sei denn, Ihr Profiler sagt etwas anderes. Erstellen Sie keine Klassen auf Funktionsebene, es sei denn, Sie wissen, warum Sie sie benötigen. Wenn Ihre API eine dictVerwendung erfordert dict, hat dies nichts mit der Leistung zu tun.
JFS
27

Beachten Sie zunächst, dass Python Folgendes zulässt (die Klammer ist nicht erforderlich):

q, r = divide(22, 7)

In Bezug auf Ihre Frage gibt es keine feste Regel. Für einfache (und normalerweise erfundene) Beispiele scheint es immer möglich zu sein, dass eine bestimmte Funktion einen einzigen Zweck hat, was zu einem einzigen Wert führt. Wenn Sie Python jedoch für reale Anwendungen verwenden, stoßen Sie schnell auf viele Fälle, in denen die Rückgabe mehrerer Werte erforderlich ist, und führen zu saubererem Code.

Also würde ich sagen, mach was auch immer Sinn macht und versuche nicht, einer künstlichen Konvention zu entsprechen. Python unterstützt mehrere Rückgabewerte. Verwenden Sie diese daher gegebenenfalls.

Jason Etheridge
quelle
4
Komma erzeugt Tupel, daher Ausdruck: q, r ist ein Tupel.
JFS
Vinko, ein Beispiel ist tempfile.mkstemp () aus der Standardbibliothek, die ein Tupel zurückgibt, das ein Dateihandle und einen absoluten Pfad enthält. JFS, ja, du hast recht.
Jason Etheridge
Aber das ist ein einziger Zweck ... Sie können einen einzigen Zweck und mehrere Rückgabewerte haben
Vinko Vrsalovic
Wenn andere Sprachen mehrere Rückgabetypen haben könnten, würde ich mich nicht auf Referenzen und 'out'-Parameter beschränken.
Orip
Das Zurückgeben mehrerer Werte ist eine der größten Funktionen in Python!
Martin Beckett
13

Das Beispiel, das Sie geben, ist eine in Python integrierte Funktion namens divmod. Irgendwann dachte jemand, es sei pythonisch genug, um es in die Kernfunktionalität aufzunehmen.

Für mich ist es pythonisch, wenn es den Code sauberer macht. Vergleichen Sie diese beiden Codeblöcke:

seconds = 1234
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)

seconds = 1234
minutes = seconds / 60
seconds = seconds % 60
hours = minutes / 60
minutes = minutes % 60
Nathan Jones
quelle
3

Ja, die Rückgabe mehrerer Werte (dh eines Tupels) ist definitiv pythonisch. Wie andere bereits betont haben, gibt es in der Python-Standardbibliothek sowie in angesehenen Python-Projekten zahlreiche Beispiele. Zwei zusätzliche Kommentare:

  1. Die Rückgabe mehrerer Werte ist manchmal sehr, sehr nützlich. Nehmen Sie zum Beispiel eine Methode, die optional ein Ereignis behandelt (dabei einen Wert zurückgibt) und auch Erfolg oder Misserfolg zurückgibt. Dies kann in einem Verantwortungskettenmuster auftreten. In anderen Fällen möchten Sie mehrere eng verknüpfte Daten zurückgeben - wie im angegebenen Beispiel. In dieser Einstellung entspricht die Rückgabe mehrerer Werte der Rückgabe einer einzelnen Instanz einer anonymen Klasse mit mehreren Mitgliedsvariablen.
  2. Pythons Umgang mit Methodenargumenten erfordert die Fähigkeit, mehrere Werte direkt zurückzugeben. In C ++ können beispielsweise Methodenargumente als Referenz übergeben werden, sodass Sie ihnen zusätzlich zum formalen Rückgabewert Ausgabewerte zuweisen können. In Python werden Argumente "als Referenz" übergeben (jedoch im Sinne von Java, nicht von C ++). Sie können Methodenargumenten keine neuen Werte zuweisen und diese außerhalb des Methodenbereichs widerspiegeln. Beispielsweise:

    // C++
    void test(int& arg)
    {
        arg = 1;
    }
    
    int foo = 0;
    test(foo); // foo is now 1!

    Vergleichen mit:

    # Python
    def test(arg):
        arg = 1
    
    foo = 0
    test(foo) # foo is still 0
fällinde
quelle
"Eine Methode, die optional ein Ereignis behandelt und auch Erfolg oder Misserfolg zurückgibt" - Dafür gibt es Ausnahmen.
Nathan
Es ist allgemein anerkannt, dass Ausnahmen nur für "außergewöhnliche" Bedingungen gelten, nicht nur für Erfolg oder Misserfolg. Google "Fehlercodes gegen Ausnahmen" für einige der Diskussionen.
Sekundenlinde
1

Es ist definitiv pythonisch. Die Tatsache, dass Sie mehrere Werte von einer Funktion zurückgeben können, die Sie in einer Sprache wie C haben würden, in der Sie für jede Kombination von Typen, die Sie irgendwo zurückgeben, eine Struktur definieren müssen.

Wenn Sie jedoch den Punkt erreichen, an dem Sie etwas Verrücktes wie 10 Werte von einer einzelnen Funktion zurückgeben, sollten Sie ernsthaft in Betracht ziehen, sie in einer Klasse zu bündeln, da sie zu diesem Zeitpunkt unhandlich werden.

Vertiefung
quelle
1

OT: Algol68 von RSRE hat den merkwürdigen Operator "/: =". z.B.

INT quotient:=355, remainder;
remainder := (quotient /:= 113);

Geben Sie einen Quotienten von 3 und einen Rest von 16 an.

Hinweis: Normalerweise wird der Wert von "(x /: = y)" verworfen, da der Quotient "x" als Referenz zugewiesen wird. Im Fall von RSRE ist der zurückgegebene Wert der Rest.

vgl. Integer Arithmetic - Algol68

NevilleDNZ
quelle
0

Es ist in Ordnung, mehrere Werte mit einem Tupel für einfache Funktionen wie z divmod. Wenn es den Code lesbar macht, ist es Pythonic.

Wenn der Rückgabewert verwirrend wird, prüfen Sie, ob die Funktion zu viel leistet, und teilen Sie ihn gegebenenfalls auf. Wenn ein großes Tupel wie ein Objekt verwendet wird, machen Sie es zu einem Objekt. Erwägen Sie auch die Verwendung benannter Tupel , die Teil der Standardbibliothek in Python 2.6 sind.

Will Harris
quelle
0

Ich bin ziemlich neu in Python, aber die Tupel-Technik scheint mir sehr pythonisch. Ich hatte jedoch eine andere Idee, die die Lesbarkeit verbessern könnte. Die Verwendung eines Wörterbuchs ermöglicht den Zugriff auf die verschiedenen Werte nach Name und nicht nach Position. Beispielsweise:

def divide(x, y):
    return {'quotient': x/y, 'remainder':x%y }

answer = divide(22, 7)
print answer['quotient']
print answer['remainder']
Fred Larson
quelle
2
Tu das nicht. Sie können das Ergebnis nicht in einem Einzeiler zuordnen, es ist sehr klobig. (Es sei denn, Sie möchten das Ergebnis speichern. In diesem Fall ist es besser, dies in eine Methode zu integrieren.) Wenn Sie mit dem Diktat beabsichtigen, die Rückgabewerte selbst zu dokumentieren, a) geben Sie dem fn einen angemessen erklärenden Namen (wie " divmod ") und b) den Rest der Erklärung in eine Dokumentationszeichenfolge einfügen (dies ist der pythonische Ort, an dem sie abgelegt werden); Wenn diese Dokumentzeichenfolge länger als zwei Zeilen benötigt, deutet dies darauf hin, dass die Funktion thematisch überlastet ist und möglicherweise eine schlechte Idee ist.
smci