Gibt es in Python einen Unterschied zwischen dem Erstellen eines Generatorobjekts über einen Generatorausdruck und der Verwendung der Yield- Anweisung?
Mit Ausbeute :
def Generator(x, y):
for i in xrange(x):
for j in xrange(y):
yield(i, j)
Mit Generator Ausdruck :
def Generator(x, y):
return ((i, j) for i in xrange(x) for j in xrange(y))
Beide Funktionen geben Generatorobjekte zurück, die Tupel erzeugen, z. B. (0,0), (0,1) usw.
Irgendwelche Vorteile des einen oder anderen? Gedanken?
Danke an alle! Diese Antworten enthalten viele großartige Informationen und weitere Referenzen!
python
python-3.x
generator
yield
cschol
quelle
quelle
Antworten:
Es gibt nur geringfügige Unterschiede zwischen den beiden. Sie können das
dis
Modul verwenden, um solche Dinge selbst zu untersuchen.Bearbeiten: Meine erste Version hat den im Modulbereich in der interaktiven Eingabeaufforderung erstellten Generatorausdruck dekompiliert. Das unterscheidet sich geringfügig von der OP-Version, die in einer Funktion verwendet wird. Ich habe dies geändert, um es dem tatsächlichen Fall in der Frage anzupassen.
Wie Sie unten sehen können, enthält der "Yield" -Generator (erster Fall) drei zusätzliche Anweisungen im Setup, die sich jedoch von der ersten
FOR_ITER
nur in einer Hinsicht unterscheiden: Der "Yield" -Ansatz verwendet einLOAD_FAST
anstelle einesLOAD_DEREF
innerhalb der Schleife. DasLOAD_DEREF
ist "eher langsamer" alsLOAD_FAST
, daher ist die "Yield" -Version etwas schneller als der Generatorausdruck für ausreichend große Werte vonx
(der äußeren Schleife), da der Wert vony
bei jedem Durchgang etwas schneller geladen wird. Bei kleineren Wertenx
wäre dies aufgrund des zusätzlichen Overheads des Setup-Codes etwas langsamer.Es kann auch erwähnenswert sein, dass der Generatorausdruck normalerweise inline im Code verwendet wird, anstatt ihn mit dieser Funktion zu umschließen. Dies würde den Setup-Aufwand etwas verringern und den Generatorausdruck für kleinere Schleifenwerte etwas schneller halten, selbst wenn
LOAD_FAST
die "Yield" -Version ansonsten einen Vorteil hätte.In keinem Fall würde der Leistungsunterschied ausreichen, um eine Entscheidung zwischen dem einen oder anderen zu rechtfertigen. Die Lesbarkeit zählt weitaus mehr. Verwenden Sie daher diejenige, die sich für die jeweilige Situation am besten lesbar anfühlt.
quelle
LOAD_DEREF
"eher langsamer" ist. Wenn also die Leistung wirklich wichtigtimeit
wäre, wäre ein echtes Timing mit gut. Eine theoretische Analyse geht nur so weit.In diesem Beispiel nicht wirklich. Aber
yield
kann für komplexere Konstrukte verwendet werden - zum Beispiel als auch Werte aus den Anrufern annehmen kann und die Strömung als Ergebnis ändern. Lesen Sie PEP 342 für weitere Details (es ist eine interessante Technik, die Sie kennen sollten).Wie auch immer, der beste Rat ist , alles zu verwenden, was für Ihre Bedürfnisse klarer ist .
PS Hier ist ein einfaches Coroutine-Beispiel von Dave Beazley :
quelle
Es gibt keinen Unterschied für die Art der einfachen Schleifen, die Sie in einen Generatorausdruck einpassen können. Der Ertrag kann jedoch verwendet werden, um Generatoren zu erstellen, die eine viel komplexere Verarbeitung durchführen. Hier ist ein einfaches Beispiel für die Erzeugung der Fibonacci-Sequenz:
quelle
Beachten Sie bei der Verwendung einen Unterschied zwischen einem Generatorobjekt und einer Generatorfunktion.
Ein Generatorobjekt kann nur einmal verwendet werden, im Gegensatz zu einer Generatorfunktion, die bei jedem erneuten Aufruf wiederverwendet werden kann, da ein neues Generatorobjekt zurückgegeben wird.
Generatorausdrücke werden in der Praxis normalerweise "roh" verwendet, ohne sie in eine Funktion einzuschließen, und sie geben ein Generatorobjekt zurück.
Z.B:
welche Ausgänge:
Vergleichen Sie mit einer etwas anderen Verwendung:
welche Ausgänge:
Und vergleiche mit einem Generatorausdruck:
welches auch ausgibt:
quelle
Die Verwendung
yield
ist hilfreich, wenn der Ausdruck komplizierter ist als nur verschachtelte Schleifen. Unter anderem können Sie einen speziellen ersten oder speziellen letzten Wert zurückgeben. Erwägen:quelle
Wenn Sie an Iteratoren denken, ist das
itertools
Modul:Berücksichtigen Sie für die Leistung
itertools.product(*iterables[, repeat])
quelle
Ja, da gibt es einen Unterschied.
Für den Generator Ausdruck
(x for var in expr)
,iter(expr)
aufgerufen wird , wenn der Ausdruck erstellt .Bei Verwendung
def
undyield
zum Erstellen eines Generators wie in:iter(expr)
wird noch nicht aufgerufen. Es wird nur beim Iterieren aufgerufeng
(und möglicherweise überhaupt nicht aufgerufen).Am Beispiel dieses Iterators:
Dieser Code:
während:
Da die meisten Iteratoren nicht viel tun
__iter__
, ist es leicht, dieses Verhalten zu übersehen. Ein Beispiel aus der realen Welt wäre DjangoQuerySet
, das Daten abruft__iter__
unddata = (f(x) for x in qs)
viel Zeit in Anspruch nehmen kann,def g(): for x in qs: yield f(x)
gefolgt vondata=g()
einer sofortigen Rückkehr.Weitere Informationen und die formale Definition finden Sie in PEP 289 - Generatorausdrücke .
quelle
Es gibt einen Unterschied, der in einigen Kontexten wichtig sein könnte, auf die noch nicht hingewiesen wurde. Die Verwendung
yield
verhindert, dass Siereturn
für etwas anderes als das implizite Erhöhen von StopIteration (und Coroutinen-bezogenen Dingen) verwenden .Dies bedeutet, dass dieser Code falsch geformt ist (und wenn Sie ihn einem Dolmetscher zuführen, erhalten Sie einen
AttributeError
):Auf der anderen Seite funktioniert dieser Code wie ein Zauber:
quelle