Die kanonische Methode, mehrere Werte in Sprachen zurückzugeben, die dies unterstützen, ist häufig ein Tupeln .
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, wenn die Anzahl der zurückgegebenen Werte zunimmt. Was ist, wenn Sie vier oder fünf Werte zurückgeben möchten? Sicher, Sie könnten sie weiter tupeln, aber es wird leicht zu vergessen, welcher Wert wo ist. Es ist auch ziemlich hässlich, sie auszupacken, wo immer Sie sie erhalten möchten.
Option: Verwenden eines Wörterbuchs
Der nächste logische Schritt scheint darin zu bestehen, eine Art "Datensatznotation" einzuführen. In Python ist der offensichtliche Weg, dies zu tun, mittels a dict
.
Folgendes berücksichtigen:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0': y0, 'y1': y1 ,'y2': y2}
(Um ganz klar zu sein, y0, y1 und y2 sind nur als abstrakte Bezeichner gedacht. Wie bereits erwähnt, würden Sie in der Praxis aussagekräftige Bezeichner verwenden.)
Jetzt haben wir einen Mechanismus, mit dem wir ein bestimmtes Mitglied des zurückgegebenen Objekts projizieren können. Zum Beispiel,
result['y0']
Option: Verwenden einer Klasse
Es gibt jedoch eine andere Option. Wir könnten stattdessen eine spezialisierte Struktur zurückgeben. Ich habe dies im Kontext von Python gerahmt, aber ich bin sicher, dass es auch für andere Sprachen gilt. Wenn Sie in C arbeiten, ist dies möglicherweise Ihre einzige Option. Hier geht:
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 sich die beiden vorherigen in Bezug auf die Installation vielleicht sehr ähnlich - schließlich handelt es sich { y0, y1, y2 }
nur um Einträge im internen Bereich __dict__
des ReturnValue
.
Es gibt eine zusätzliche Funktion, die Python für winzige Objekte bereitstellt, das __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
Aus dem Python-Referenzhandbuch :
Die
__slots__
Deklaration verwendet eine Folge von Instanzvariablen und reserviert in jeder Instanz gerade genug Speicherplatz, um einen Wert für jede Variable aufzunehmen. Speicherplatz wird gespart, da__dict__
nicht für jede Instanz erstellt wird.
Option: Verwenden einer Datenklasse (Python 3.7+)
Geben Sie mit den neuen Datenklassen von Python 3.7 eine Klasse mit automatisch hinzugefügten speziellen Methoden, Eingaben und anderen nützlichen Tools 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)
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 glaube, ich bin durch die Begegnung mit Haskell in Mitleidenschaft gezogen, aber die Idee von Listen mit gemischten Typen hat sich für mich immer unangenehm angefühlt. In diesem speziellen Beispiel ist die Liste kein gemischter Typ, aber es könnte denkbar sein.
Eine Liste, die auf diese Weise verwendet wird, hat, soweit ich das beurteilen kann, wirklich nichts mit dem Tupel zu tun. Der einzige wirkliche Unterschied zwischen Listen und Tupeln in Python besteht darin, dass Listen veränderlich sind , Tupel jedoch nicht.
Ich persönlich neige dazu, die Konventionen aus der funktionalen Programmierung zu übernehmen: Verwenden Sie Listen für eine beliebige Anzahl von Elementen desselben Typs und Tupel für eine feste Anzahl von Elementen eines vorgegebenen Typs.
Frage
Nach der langen Präambel kommt die unvermeidliche Frage. Welche Methode (denkst du) ist die beste?
quelle
y3
Ihren hervorragenden Beispielen verwenden Sie eine Variable , aber wenn y3 nicht als global deklariert ist, würde diesNameError: global name 'y3' is not defined
möglicherweise nur eine Verwendung ergeben3
?y3
, der den gleichen Job erledigt .Antworten:
Zu diesem Zweck wurden in 2.6 benannte Tupel hinzugefügt. Siehe auch os.stat für ein ähnliches eingebautes Beispiel.
In neueren Versionen von Python 3 (3.6+, glaube ich) hat die neue
typing
Bibliothek dieNamedTuple
Klasse erhalten, um benannte Tupel einfacher zu erstellen und leistungsfähiger zu machen. Beim Erben vontyping.NamedTuple
können Sie Dokumentzeichenfolgen, Standardwerte und Typanmerkungen verwenden.Beispiel (aus den Dokumenten):
quelle
namedtuple
sind ein geringerer Speicherbedarf für Massenergebnisse (lange Listen von Tupeln, z. B. Ergebnisse von DB-Abfragen). Für einzelne Elemente (wenn die betreffende Funktion nicht oft aufgerufen wird) sind Wörterbücher und Klassen ebenfalls in Ordnung. Aber Namedtuples sind auch in diesem Fall eine schöne Lösung.namedtuple
Definitionen zu eindeutig zu machen (jeder Aufruf erstellt eine neue). Das Erstellen dernamedtuple
Klasse ist sowohl in der CPU als auch im Speicher relativ teuer, und alle Klassendefinitionen beinhalten an sich zyklische Referenzen (bei CPython warten Sie also auf einen zyklischen GC-Lauf für ihre Freilassung). Dies macht es auch fürpickle
die Klasse unmöglich (und dahermultiprocessing
in den meisten Fällen unmöglich, Instanzen mit zu verwenden ). Jede Erstellung der Klasse auf meinem 3.6.4 x64 verbraucht ~ 0,337 ms und belegt knapp 1 KB Speicher, wodurch alle Instanzeinsparungen zunichte gemacht werden.namedtuple
Klassen verbessert hat . Die CPU-Kosten sinken ungefähr um den Faktor 4 , aber sie sind immer noch ungefähr 1000x höher als die Kosten für die Erstellung einer Instanz, und die Speicherkosten für jede Klasse bleiben hoch (ich habe mich in meinem letzten Kommentar zu "unter 1 KB" geirrt). Für die Klasse_source
beträgt sie in der Regel 1,5 KB (_source
wird in 3.7 entfernt, sodass sie wahrscheinlich näher an der ursprünglichen Behauptung von knapp 1 KB pro Klassenerstellung liegt).Für kleine Projekte 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. Ich denke jedoch, dass Ihre vorgeschlagene Verwendung von Wörterbüchern und
ReturnValue
Objekten falsch (oder zu simpel) ist.Wiederkehrende ein Wörterbuch mit den Tasten
"y0"
,"y1"
,"y2"
etc. bietet keinen Vorteil gegenüber Tupel. Zurückgeben einerReturnValue
Instanz mit Eigenschaften.y0
,.y1
,.y2
etc. bietet keinen Vorteil gegenüber Tupeln entweder. Sie müssen anfangen, Dinge zu benennen, wenn Sie irgendwohin wollen, und Sie können dies trotzdem mit Tupeln tun:IMHO ist die einzig gute Technik jenseits von Tupeln die Rückgabe realer Objekte mit geeigneten Methoden und Eigenschaften, wie Sie sie von
re.match()
oder erhaltenopen(file)
.quelle
size, type, dimensions = getImageData(x)
und(size, type, dimensions) = getImageData(x)
? Dh, macht das Umschließen der linken Seite einer Tupelaufgabe einen Unterschied?(1)
ist ein int while(1,)
oder1,
ein Tupel.Viele der Antworten deuten darauf hin, dass Sie eine Sammlung wie ein Wörterbuch oder eine Liste zurückgeben müssen. Sie können die zusätzliche Syntax weglassen und die durch Kommas getrennten Rückgabewerte einfach ausschreiben. Hinweis: Dies gibt technisch ein Tupel zurück.
gibt:
quelle
type(f())
kehrt zurück<class 'tuple'>
.tuple
Aspekt explizit zu machen . Es ist nicht wirklich wichtig, dass Sie a zurückgebentuple
. Dies ist die Redewendung für die Rückgabe mehrerer Werte. Aus demselben Grund lassen Sie die Parens mit der Swap-Sprachex, y = y, x
, der Mehrfachinitialisierungx, y = 0, 1
usw.; Sicher, es machttuple
s unter der Haube, aber es gibt keinen Grund, dies explizit zu machen, da dastuple
s überhaupt nicht der Punkt ist. Das Python-Tutorial führt mehrere Aufgaben ein, lange bevor estuple
s berührt .=
ist in Python ein Tupel mit oder ohne Klammern. Es gibt hier also eigentlich keine expliziten oder impliziten. a, b, c ist ebenso ein Tupel wie (a, b, c). Es gibt auch kein Tupel "unter der Haube", wenn Sie solche Werte zurückgeben, weil es nur ein einfaches Tupel ist. Das OP erwähnte bereits Tupel, so dass es tatsächlich keinen Unterschied zwischen dem, was er erwähnte, und dem, was diese Antwort zeigt, gibt. KeineIch stimme für das Wörterbuch.
Ich finde, wenn ich eine Funktion erstelle, die mehr als 2-3 Variablen zurückgibt, falte ich sie in einem Wörterbuch zusammen. Ansonsten neige ich dazu, die Reihenfolge und den Inhalt meiner Rücksendung zu vergessen.
Durch die Einführung einer speziellen Struktur wird es außerdem schwieriger, Ihrem Code zu folgen. (Jemand anderes muss den Code durchsuchen, um herauszufinden, was er ist.)
Wenn Sie Bedenken hinsichtlich der Typensuche haben, verwenden Sie beschreibende Wörterbuchschlüssel, z. B. "Liste der x-Werte".
quelle
result = g(x); other_function(result)
Eine andere Option wäre die Verwendung von Generatoren:
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.
quelle
yield
?def f(x): …; yield b; yield a; yield r
vs.(g for g in [b, a, r])
und beide werden leicht in Listen oder Tupel konvertiert und unterstützen als solche das Entpacken von Tupeln. Die Tupelgeneratorform folgt einem funktionalen Ansatz, während die Funktionsform unbedingt erforderlich ist und eine Flusssteuerung und Variablenzuweisung ermöglicht.Ich bevorzuge es, Tupel zu verwenden, wenn sich ein Tupel "natürlich" anfühlt. Koordinaten sind ein typisches Beispiel, bei dem die einzelnen Objekte für sich allein stehen können, z. B. bei nur einachsigen 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, in denen 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.
quelle
Ich bevorzuge:
Es scheint, dass alles andere nur zusätzlicher Code ist, um dasselbe zu tun.
quelle
quelle
Im Allgemeinen ist die "spezialisierte Struktur" tatsächlich ein vernünftiger aktueller Zustand eines Objekts mit eigenen Methoden.
Ich finde gerne Namen für anonyme Strukturen, wo dies möglich ist. Bedeutungsvolle Namen machen die Dinge klarer.
quelle
Pythons Tupel, Diktate und Objekte bieten dem Programmierer einen reibungslosen Kompromiss zwischen Formalität und Komfort für kleine Datenstrukturen ("Dinge"). Für mich hängt die Wahl, wie eine Sache dargestellt werden soll, hauptsächlich davon ab, wie ich die Struktur verwenden werde. In C ++ ist es eine übliche Konvention,
struct
nur für Datenelemente undclass
für Objekte mit Methoden zu verwenden, obwohl Sie Methoden legal auf a setzen könnenstruct
. Meine Gewohnheit ist in Python ähnlich, mitdict
undtuple
anstelle vonstruct
.Für Koordinatensätze verwende ich
tuple
eher einen als einen Punktclass
oder einendict
(und beachte, dass Sie einentuple
als Wörterbuchschlüssel verwenden können,dict
um großartige, spärliche mehrdimensionale Arrays zu erstellen).Wenn ich eine Liste von Dingen durchlaufen möchte, entpacke ich lieber
tuple
s in der Iteration:... da die Objektversion unübersichtlicher zu lesen ist:
... geschweige denn die
dict
.Wenn das Ding weit verbreitet ist und Sie an mehreren Stellen im Code ähnliche, nicht triviale Operationen ausführen, lohnt es sich normalerweise, es zu einem Klassenobjekt mit geeigneten Methoden zu machen.
Wenn ich Daten mit Nicht-Python-Systemkomponenten austauschen möchte, behalte ich sie meistens in einem,
dict
da dies für die JSON-Serialisierung am besten geeignet ist.quelle
+1 auf S.Lotts Vorschlag einer benannten Containerklasse.
Ab Python 2.6 bietet ein benanntes Tupel eine nützliche Möglichkeit, diese Containerklassen einfach zu erstellen. Die Ergebnisse sind "leichtgewichtig und erfordern nicht mehr Speicher als normale Tupel".
quelle
In Sprachen wie Python würde ich normalerweise ein Wörterbuch verwenden, da es weniger Aufwand erfordert als das Erstellen einer neuen Klasse.
Wenn ich jedoch ständig denselben Satz von Variablen zurückgebe, handelt es sich wahrscheinlich um eine neue Klasse, die ich herausrechnen werde.
quelle
Ich würde ein Diktat verwenden, um Werte von einer Funktion zu übergeben und zurückzugeben:
Verwenden Sie die im Formular definierte variable Form .
Dies ist der effizienteste Weg für mich und für die Verarbeitungseinheit.
Sie müssen nur einen Zeiger übergeben und nur einen Zeiger zurückgeben.
Sie müssen die Argumente von Funktionen (Tausende von ihnen) nicht ändern, wenn Sie Änderungen an Ihrem Code vornehmen.
quelle
test
der Wert direkt geändert wird. Vergleichen Sie dies mitdict.update
, das keinen Wert zurückgibt.form
jedoch weder die Lesbarkeit noch die Leistung. In Fällen, in denen Sie möglicherweise eine Neuformatierung durchführen müssenform
, stellt die Rückgabe des Formulars sicher, dass das letzteform
zurückgegeben wird, da Sie die Formularänderungen nirgendwo verfolgen können."Best" ist eine teilweise subjektive Entscheidung. Verwenden Sie Tupel für kleine Rückgabesätze im allgemeinen Fall, wenn eine unveränderliche Menge akzeptabel ist. Ein Tupel ist einer Liste immer vorzuziehen, wenn keine Veränderlichkeit erforderlich ist.
Für komplexere Rückgabewerte oder für den Fall, dass Formalität wertvoll ist (dh Code mit hohem Wert), ist ein benanntes Tupel besser. Für den komplexesten Fall ist ein Objekt normalerweise am besten. Es ist jedoch wirklich die Situation, die zählt. Wenn es sinnvoll ist, ein Objekt zurückzugeben, weil Sie dies natürlich am Ende der Funktion haben (z. B. Factory-Muster), geben Sie das Objekt zurück.
Wie der Weise sagte:
quelle