Um zu verstehen, was yield
funktioniert, müssen Sie verstehen, was Generatoren sind. Und bevor Sie Generatoren verstehen können, müssen Sie iterables verstehen .
Iterables
Wenn Sie eine Liste erstellen, können Sie deren Elemente einzeln lesen. Das Lesen der Elemente nacheinander wird als Iteration bezeichnet:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
ist eine iterable . Wenn Sie ein Listenverständnis verwenden, erstellen Sie eine Liste und damit eine iterierbare:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
Alles, wofür Sie " for... in...
" verwenden können, ist iterierbar. lists
, strings
, Dateien ...
Diese iterablen Dateien sind praktisch, da Sie sie beliebig oft lesen können, aber alle Werte im Speicher speichern. Dies ist nicht immer das, was Sie möchten, wenn Sie viele Werte haben.
Generatoren
Generatoren sind Iteratoren, eine Art Iterierbarkeit, die Sie nur einmal durchlaufen können . Generatoren speichern nicht alle Werte im Speicher, sie generieren die Werte im laufenden Betrieb :
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
Es ist genau das gleiche, außer dass Sie ()
anstelle von verwendet haben []
. ABER Sie können keinfor i in mygenerator
zweites Mal ausführen, da Generatoren nur einmal verwendet werden können: Sie berechnen 0, vergessen es dann und berechnen 1 und beenden die Berechnung von 4 nacheinander.
Ausbeute
yield
ist ein Schlüsselwort, das wie folgt verwendet wird return
, außer dass die Funktion einen Generator zurückgibt.
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
Hier ist es ein nutzloses Beispiel, aber es ist praktisch, wenn Sie wissen, dass Ihre Funktion eine große Anzahl von Werten zurückgibt, die Sie nur einmal lesen müssen.
Um zu beherrschen yield
, müssen Sie verstehen, dass beim Aufrufen der Funktion der Code, den Sie in den Funktionskörper geschrieben haben, nicht ausgeführt wird. Die Funktion gibt nur das Generatorobjekt zurück, das ist etwas knifflig :-)
Dann wird Ihr Code bei jeder for
Verwendung des Generators dort fortgesetzt, wo er aufgehört hat.
Nun der schwierige Teil:
for
Wenn Sie das aus Ihrer Funktion erstellte Generatorobjekt zum ersten Mal aufrufen, wird der Code in Ihrer Funktion von Anfang an ausgeführt, bis er trifft yield
. Anschließend wird der erste Wert der Schleife zurückgegeben. Dann führt jeder nachfolgende Aufruf eine weitere Iteration der Schleife aus, die Sie in die Funktion geschrieben haben, und gibt den nächsten Wert zurück. Dies wird fortgesetzt, bis der Generator als leer betrachtet wird. Dies geschieht, wenn die Funktion ohne Treffer ausgeführt wird yield
. Das kann daran liegen, dass die Schleife beendet ist oder dass Sie eine nicht mehr erfüllen "if/else"
.
Ihr Code erklärt
Generator:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if the distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if the distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
Anrufer:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidate's list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
Dieser Code enthält mehrere intelligente Teile:
Die Schleife iteriert in einer Liste, aber die Liste wird erweitert, während die Schleife iteriert wird :-) Dies ist eine übersichtliche Methode, um alle diese verschachtelten Daten zu durchlaufen, auch wenn dies etwas gefährlich ist, da Sie möglicherweise eine Endlosschleife erhalten. In diesem Fall candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
erschöpfen Sie alle Werte des Generators, while
erstellen jedoch weiterhin neue Generatorobjekte, die andere Werte als die vorherigen erzeugen, da sie nicht auf denselben Knoten angewendet werden.
Die extend()
Methode ist eine Listenobjektmethode, die eine Iterierbarkeit erwartet und ihre Werte zur Liste hinzufügt.
Normalerweise übergeben wir ihm eine Liste:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
Aber in Ihrem Code bekommt es einen Generator, was gut ist, weil:
- Sie müssen die Werte nicht zweimal lesen.
- Möglicherweise haben Sie viele Kinder und möchten nicht, dass alle im Speicher gespeichert werden.
Und es funktioniert, weil es Python egal ist, ob das Argument einer Methode eine Liste ist oder nicht. Python erwartet Iterables, damit es mit Zeichenfolgen, Listen, Tupeln und Generatoren funktioniert! Dies wird als Ententypisierung bezeichnet und ist einer der Gründe, warum Python so cool ist. Aber das ist eine andere Geschichte, für eine andere Frage ...
Sie können hier anhalten oder ein wenig lesen, um eine erweiterte Verwendung eines Generators zu sehen:
Steuerung einer Generatorerschöpfung
>>> class Bank(): # Let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
Hinweis: Verwenden Sie für Python 3 print(corner_street_atm.__next__())
oderprint(next(corner_street_atm))
Dies kann für verschiedene Zwecke nützlich sein, z. B. zum Steuern des Zugriffs auf eine Ressource.
Itertools, dein bester Freund
Das Modul itertools enthält spezielle Funktionen zum Bearbeiten von Iterables. Möchten Sie jemals einen Generator duplizieren? Kette zwei Generatoren? Werte in einer verschachtelten Liste mit einem Einzeiler gruppieren? Map / Zip
ohne eine andere Liste zu erstellen?
Dann einfach import itertools
.
Ein Beispiel? Sehen wir uns die möglichen Ankunftsreihenfolgen für ein Vierpferderennen an:
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
Die inneren Mechanismen der Iteration verstehen
Iteration ist ein Prozess, der Iterables (Implementierung der __iter__()
Methode) und Iteratoren (Implementierung der __next__()
Methode) impliziert . Iterables sind alle Objekte, von denen Sie einen Iterator erhalten können. Iteratoren sind Objekte, mit denen Sie iterable iterieren können.
In diesem Artikel erfahren Sie mehr darüber, wie for
Schleifen funktionieren .
yield
ist nicht so magisch, wie diese Antwort nahelegt. Wenn Sie eine Funktion aufrufen, dieyield
irgendwo eine Anweisung enthält , erhalten Sie ein Generatorobjekt, aber es wird kein Code ausgeführt. Jedes Mal, wenn Sie ein Objekt aus dem Generator extrahieren, führt Python Code in der Funktion aus, bis eineyield
Anweisung vorliegt , hält das Objekt an und liefert es aus. Wenn Sie ein anderes Objekt extrahieren, wird Python direkt nach demyield
fortgesetzt und fortgesetzt, bis es ein anderes erreichtyield
(häufig dasselbe, aber eine Iteration später). Dies wird fortgesetzt, bis die Funktion am Ende abläuft. An diesem Punkt gilt der Generator als erschöpft.()
anstelle von verwendet haben[]
, insbesondere was es()
ist (es kann Verwechslungen mit einem Tupel geben).Abkürzung zum Verständnis
yield
Wenn Sie eine Funktion mit
yield
Anweisungen sehen, wenden Sie diesen einfachen Trick an, um zu verstehen, was passieren wird:result = []
am Anfang der Funktion eine Zeile ein .yield expr
durchresult.append(expr)
.return result
am unteren Rand der Funktion eine Zeile ein .yield
Aussagen mehr ! Code lesen und herausfinden.Dieser Trick gibt Ihnen vielleicht eine Vorstellung von der Logik hinter der Funktion, aber was passiert eigentlich damit?
yield
unterscheidet sich erheblich von dem, was beim listenbasierten Ansatz passiert. In vielen Fällen ist der Ertragsansatz viel speichereffizienter und auch schneller. In anderen Fällen werden Sie mit diesem Trick in einer Endlosschleife stecken bleiben, obwohl die ursprüngliche Funktion einwandfrei funktioniert. Lesen Sie weiter, um mehr zu erfahren ...Verwechseln Sie nicht Ihre Iterables, Iteratoren und Generatoren
Erstens das Iteratorprotokoll - wenn Sie schreiben
Python führt die folgenden zwei Schritte aus:
Ruft einen Iterator für ab
mylist
:Aufruf
iter(mylist)
-> Dies gibt ein Objekt mit einernext()
Methode zurück (oder__next__()
in Python 3).[Dies ist der Schritt, von dem die meisten Leute vergessen, Ihnen zu erzählen]
Verwendet den Iterator, um Elemente zu durchlaufen:
Rufen Sie die
next()
Methode für den aus Schritt 1 zurückgegebenen Iterator weiter auf. Der Rückgabewert vonnext()
wird zugewiesenx
und der Schleifenkörper wird ausgeführt. Wenn eine AusnahmeStopIteration
von innen ausgelöst wirdnext()
, bedeutet dies, dass der Iterator keine Werte mehr enthält und die Schleife beendet wird.Die Wahrheit ist , Python , die über zwei Stufen zu jeder Zeit führt es will Schleife über den Inhalt eines Objekts - so ist es eine for - Schleife sein könnte, aber es könnte auch Code sein wie
otherlist.extend(mylist)
(wootherlist
ist ein Python - Liste).Hier
mylist
ist eine iterable, weil es das Iterator-Protokoll implementiert. In einer benutzerdefinierten Klasse können Sie die__iter__()
Methode implementieren , um Instanzen Ihrer Klasse iterierbar zu machen. Diese Methode sollte einen Iterator zurückgeben . Ein Iterator ist ein Objekt mit einernext()
Methode. Es ist möglich, beide__iter__()
undnext()
dieselbe Klasse zu implementieren und eine__iter__()
Rückgabe zu erhaltenself
. Dies funktioniert in einfachen Fällen, jedoch nicht, wenn zwei Iteratoren gleichzeitig dasselbe Objekt durchlaufen sollen.Das ist also das Iteratorprotokoll. Viele Objekte implementieren dieses Protokoll:
__iter__()
.Beachten Sie, dass eine
for
Schleife nicht weiß, um welche Art von Objekt es sich handelt - sie folgt nur dem Iteratorprotokoll und freut sich, beim Aufrufen Element für Element zu erhaltennext()
. Eingebaute Listen geben ihre Elemente einzeln zurück, Wörterbücher geben die Schlüssel einzeln zurück, Dateien geben die Zeilen einzeln zurück usw. Und Generatoren kehren zurück ... nun, hieryield
kommt Folgendes ins Spiel:yield
Wenn Sie anstelle von Anweisungen dreireturn
Anweisungenf123()
hätten, würde nur die erste ausgeführt und die Funktion beendet. Istf123()
aber keine gewöhnliche Funktion. Wennf123()
es aufgerufen wird, gibt es keinen der Werte in den Yield-Anweisungen zurück! Es gibt ein Generatorobjekt zurück. Außerdem wird die Funktion nicht wirklich beendet - sie wird angehalten. Wenn diefor
Schleife versucht, das Generatorobjekt zu durchlaufen, wird die Funktion in der nächsten Zeile nach deryield
vorherigen Rückgabe aus ihrem angehaltenen Zustand fortgesetzt, die nächste Codezeile, in diesem Fall eineyield
Anweisung, ausgeführt und als nächste zurückgegeben Artikel. Dies geschieht bis zum Beenden der Funktion. An diesem Punkt wird der Generator angehobenStopIteration
wird und die Schleife beendet wird.Das Generatorobjekt ähnelt also einem Adapter - an einem Ende zeigt es das Iteratorprotokoll, indem es
__iter__()
undnext()
Methoden verfügbar macht , um diefor
Schleife glücklich zu machen. Am anderen Ende wird die Funktion jedoch gerade so weit ausgeführt, dass der nächste Wert daraus hervorgeht, und sie wird wieder in den angehaltenen Modus versetzt.Warum Generatoren verwenden?
Normalerweise können Sie Code schreiben, der keine Generatoren verwendet, aber dieselbe Logik implementiert. Eine Möglichkeit besteht darin, den zuvor erwähnten temporären Listentrick zu verwenden. Dies funktioniert nicht in allen Fällen, z. B. wenn Sie Endlosschleifen haben, oder wenn Sie eine wirklich lange Liste haben, wird der Speicher möglicherweise ineffizient genutzt. Der andere Ansatz besteht darin, eine neue iterierbare Klasse SomethingIter zu implementieren, die den Status in Instanzmitgliedern beibehält und den nächsten logischen Schritt in ihrer
next()
(oder__next__()
in Python 3) Methode ausführt . Abhängig von der Logiknext()
sieht der Code in der Methode möglicherweise sehr komplex aus und ist anfällig für Fehler. Hier bieten Generatoren eine saubere und einfache Lösung.quelle
send
in einen Generator einsteigen können , der einen großen Teil des Punktes von Generatoren ausmacht?otherlist.extend(mylist)
" -> Dies ist falsch.extend()
Ändert die Liste an Ort und Stelle und gibt keine iterable zurück. Der Versuch, eine Schleifeotherlist.extend(mylist)
durchzuführen, schlägt mit a fehl,TypeError
daextend()
implizit zurückgegebenNone
wird und Sie keine Schleife durchführen könnenNone
.mylist
(nichtotherlist
aktiviert) hatotherlist.extend(mylist)
.Denk darüber so:
Ein Iterator ist nur ein ausgefallener Begriff für ein Objekt mit einer
next()
Methode. Eine Funktion mit Ertrag ist also ungefähr so:Originalfassung:
Dies ist im Grunde das, was der Python-Interpreter mit dem obigen Code macht:
Um mehr darüber zu erfahren, was hinter den Kulissen passiert, kann die
for
Schleife folgendermaßen umgeschrieben werden:Ist das sinnvoller oder verwirrt es Sie nur mehr? :) :)
Ich sollte anmerken , dass dies ist eine grobe Vereinfachung zu illustrativen Zwecken. :) :)
quelle
__getitem__
könnte anstelle von definiert werden__iter__
. Zum Beispiel :class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i)
, Es wird gedruckt: 0, 10, 20, ..., 90iterator = some_function()
, hat die Variableiterator
keine Funktionnext()
mehr, sondern nur noch eine__next__()
Funktion. Ich dachte, ich würde es erwähnen.for
Ihnen geschriebene Schleifenimplementierung die__iter__
Methode von aufiterator
, die instanziierte Instanz vonit
?Das
yield
Schlüsselwort reduziert sich auf zwei einfache Fakten:yield
Schlüsselwort irgendwo in einer Funktion erkennt , wird diese Funktion nicht mehr über diereturn
Anweisung zurückgegeben. Stattdessen wird sofort ein verzögertes Objekt "Ausstehende Liste" zurückgegeben, das als Generator bezeichnet wirdlist
oderset
oderrange
oder eine Diktatansicht mit einem integrierten Protokoll zum Aufrufen jedes Elements in einer bestimmten Reihenfolge .Kurz gesagt: Ein Generator ist eine faule, inkrementell anstehende Liste . Mit
yield
Anweisungen können Sie mithilfe der Funktionsnotation die Listenwerte programmieren, die der Generator schrittweise ausspucken soll.Beispiel
Definieren wir eine Funktion
makeRange
, die der von Python ähneltrange
. Der AufrufmakeRange(n)
RÜCKKEHR A GENERATOR:Um den Generator zu zwingen, seine ausstehenden Werte sofort zurückzugeben, können Sie ihn übergeben
list()
(genau wie bei jedem iterierbaren Element):Vergleich des Beispiels mit "nur eine Liste zurückgeben"
Das obige Beispiel kann als bloße Erstellung einer Liste betrachtet werden, an die Sie anhängen und die Sie zurückgeben:
Es gibt jedoch einen großen Unterschied; siehe den letzten Abschnitt.
Wie Sie Generatoren verwenden könnten
Eine Iterierbarkeit ist der letzte Teil eines Listenverständnisses, und alle Generatoren sind iterierbar, daher werden sie häufig folgendermaßen verwendet:
Um ein besseres Gefühl für Generatoren zu bekommen, können Sie mit dem
itertools
Modul herumspielen (verwenden Sie eschain.from_iterable
nicht,chain
wenn dies gerechtfertigt ist). Zum Beispiel könnten Sie sogar Generatoren verwenden, um unendlich lange Lazy-Listen wie zu implementierenitertools.count()
. Sie können Ihre eigene implementierendef enumerate(iterable): zip(count(), iterable)
oder dies alternativ mit demyield
Schlüsselwort in einer while-Schleife tun .Bitte beachten Sie: Generatoren können tatsächlich für viele weitere Dinge verwendet werden, z. B. für die Implementierung von Coroutinen oder nicht deterministischer Programmierung oder andere elegante Dinge. Der hier vorgestellte Standpunkt "Lazy Lists" ist jedoch die häufigste Verwendung, die Sie finden werden.
Hinter den Kulissen
So funktioniert das "Python-Iterationsprotokoll". Das heißt, was ist los, wenn Sie es tun
list(makeRange(5))
. Dies ist, was ich früher als "faule, inkrementelle Liste" beschrieben habe.Die eingebaute Funktion
next()
ruft nur die Objektfunktion auf.next()
, die Teil des "Iterationsprotokolls" ist und auf allen Iteratoren zu finden ist. Sie können dienext()
Funktion (und andere Teile des Iterationsprotokolls) manuell verwenden , um ausgefallene Dinge zu implementieren, normalerweise auf Kosten der Lesbarkeit. Versuchen Sie also, dies zu vermeiden ...Minutien
Normalerweise interessieren sich die meisten Menschen nicht für die folgenden Unterscheidungen und möchten wahrscheinlich hier aufhören zu lesen.
In Python-speak ist eine Iterable jedes Objekt, das "das Konzept einer for-Schleife versteht" wie eine Liste
[1,2,3]
, und ein Iterator ist eine bestimmte Instanz der angeforderten for-Schleife wie[1,2,3].__iter__()
. Ein Generator ist genau der gleiche wie jeder Iterator, außer wie er geschrieben wurde (mit Funktionssyntax).Wenn Sie einen Iterator aus einer Liste anfordern, wird ein neuer Iterator erstellt. Wenn Sie jedoch einen Iterator von einem Iterator anfordern (was Sie selten tun würden), erhalten Sie nur eine Kopie von sich.
In dem unwahrscheinlichen Fall, dass Sie so etwas nicht tun ...
... dann denken Sie daran, dass ein Generator ein Iterator ist ; Das heißt, es ist eine einmalige Verwendung. Wenn Sie es wiederverwenden möchten, sollten Sie
myRange(...)
erneut anrufen . Wenn Sie das Ergebnis zweimal verwenden müssen, konvertieren Sie das Ergebnis in eine Liste und speichern Sie es in einer Variablenx = list(myRange(5))
. Diejenigen, die unbedingt einen Generator klonen müssen (z. B. die furchtbar hackige Metaprogrammierung durchführen), können dieseitertools.tee
bei Bedarf verwenden, da der Vorschlag für kopierbare Python- PEP- Standards für Iteratoren zurückgestellt wurde.quelle
Antwort Gliederung / Zusammenfassung
yield
gibt beim Aufruf einen Generator zurück .yield from
.return
in einem Generator.)Generatoren:
yield
ist nur innerhalb einer Funktionsdefinition zulässig, und durch die Aufnahmeyield
in eine Funktionsdefinition wird ein Generator zurückgegeben.Die Idee für Generatoren stammt aus anderen Sprachen (siehe Fußnote 1) mit unterschiedlichen Implementierungen. In Pythons Generatoren ist die Ausführung des Codes eingefroren zum Zeitpunkt der Ausbeute . Wenn der Generator aufgerufen wird (Methoden werden unten erläutert), wird die Ausführung fortgesetzt und friert bei der nächsten Ausbeute ein.
yield
bietet eine einfache Möglichkeit zur Implementierung des Iteratorprotokolls , das durch die folgenden zwei Methoden definiert wird:__iter__
undnext
(Python 2) oder__next__
(Python 3). Beide Methoden machen ein Objekt zu einem Iterator, den Sie mit derIterator
Abstract Base Class aus demcollections
Modul überprüfen können .Der Generatortyp ist ein Untertyp des Iterators:
Und wenn nötig, können wir das wie folgt überprüfen:
Ein Merkmal von a
Iterator
ist, dass Sie es, sobald es erschöpft ist , nicht wiederverwenden oder zurücksetzen können:Sie müssen eine andere erstellen, wenn Sie die Funktionalität erneut verwenden möchten (siehe Fußnote 2):
Man kann Daten programmgesteuert liefern, zum Beispiel:
Der obige einfache Generator entspricht auch dem folgenden - ab Python 3.3 (und nicht in Python 2 verfügbar) können Sie Folgendes verwenden
yield from
:yield from
Ermöglicht jedoch auch die Delegierung an Subgeneratoren, was im folgenden Abschnitt über die kooperative Delegation mit Subkoroutinen erläutert wird.Coroutinen:
yield
bildet einen Ausdruck, mit dem Daten an den Generator gesendet werden können (siehe Fußnote 3)Beachten Sie in diesem Beispiel die
received
Variable, die auf die Daten verweist, die an den Generator gesendet werden:Zuerst müssen wir den Generator mit der eingebauten Funktion in die Warteschlange stellen
next
. Abhängig von der verwendeten Python-Version wird die entsprechende Methodenext
oder__next__
Methode aufgerufen :Und jetzt können wir Daten in den Generator senden. ( Senden
None
ist dasselbe wie Anrufennext
.):Kooperative Delegation bei Sub-Coroutine mit
yield from
yield from
Denken Sie jetzt daran, dass dies in Python 3 verfügbar ist. Auf diese Weise können wir Coroutinen an eine Subkoroutine delegieren:Und jetzt können wir die Funktionalität an einen Untergenerator delegieren und sie kann wie oben von einem Generator verwendet werden:
Weitere Informationen zur genauen Semantik finden Sie
yield from
in PEP 380.Andere Methoden: schließen und werfen
Die
close
Methode wirdGeneratorExit
an dem Punkt ausgelöst, an dem die Funktionsausführung eingefroren wurde. Dies wird auch von aufgerufen,__del__
damit Sie jeden Bereinigungscode dort ablegen können, wo Sie Folgendes ausführenGeneratorExit
:Sie können auch eine Ausnahme auslösen, die im Generator behandelt oder an den Benutzer zurückgegeben werden kann:
Fazit
Ich glaube, ich habe alle Aspekte der folgenden Frage behandelt:
Es stellt sich heraus, dass
yield
das viel bewirkt. Ich bin sicher, ich könnte dem noch gründlichere Beispiele hinzufügen. Wenn Sie mehr wollen oder konstruktive Kritik haben, lassen Sie es mich wissen, indem Sie unten einen Kommentar abgeben.Blinddarm:
Kritik der Top / Akzeptierte Antwort **
__iter__
Methode, die einen Iterator zurückgibt . Ein Iterator stellt eine.next
(Python 2- oder.__next__
(Python 3) -Methode bereit , die implizit vonfor
Schleifen aufgerufen wird , bis sie ausgelöst wirdStopIteration
wird. Sobald dies der Fall ist, wird dies auch weiterhin der Fall sein.yield
Teil gekommen..next
Methode auf, wenn er stattdessen die eingebaute Funktion verwenden sollnext
. Dies wäre eine geeignete Indirektionsebene, da sein Code in Python 3 nicht funktioniert.yield
tut.yield
die zusammen mit der neuen Funktionalitätyield from
in Python 3 bereitgestellt werden. Die oberste / akzeptierte Antwort ist eine sehr unvollständige Antwort.Antwortkritik, die
yield
in einem Generatorausdruck oder -verständnis vorschlägt .Die Grammatik erlaubt derzeit jeden Ausdruck in einem Listenverständnis.
Da Ausbeute ein Ausdruck ist, wurde es von einigen als interessant angepriesen, ihn im Verständnis oder im Generatorausdruck zu verwenden - obwohl kein besonders guter Anwendungsfall angeführt wurde.
Die CPython-Kernentwickler diskutieren, die Zulässigkeit zu verwerfen . Hier ist ein relevanter Beitrag aus der Mailingliste:
Darüber hinaus gibt es ein offenes Problem (10544), das darauf hinzudeuten scheint, dass dies niemals eine gute Idee ist (PyPy, eine in Python geschriebene Python-Implementierung, gibt bereits Syntaxwarnungen aus.)
Fazit: Bis die Entwickler von CPython uns etwas anderes sagen: Geben Sie keinen
yield
generatorischen Ausdruck oder Verständnis ein.Die
return
Aussage in einem GeneratorIn Python 2 :
An
expression_list
ist im Grunde eine beliebige Anzahl von Ausdrücken, die durch Kommas getrennt sind. Im Wesentlichen können Sie in Python 2 den Generator stoppenreturn
, aber keinen Wert zurückgeben.In Python 3 :
Fußnoten
In dem Vorschlag, das Konzept der Generatoren in Python einzuführen, wurde auf die Sprachen CLU, Sather und Icon verwiesen. Die allgemeine Idee ist, dass eine Funktion den internen Zustand beibehalten und auf Anforderung des Benutzers Zwischendatenpunkte liefern kann. Dies versprach eine überlegene Leistung gegenüber anderen Ansätzen, einschließlich Python-Threading , das auf einigen Systemen nicht einmal verfügbar ist.
Dies bedeutet beispielsweise, dass
xrange
Objekte (range
in Python 3) keineIterator
s sind, obwohl sie iterierbar sind, da sie wiederverwendet werden können. Wie Listen geben ihre__iter__
Methoden Iteratorobjekte zurück.yield
wurde ursprünglich als Anweisung eingeführt, was bedeutet, dass sie nur am Anfang einer Zeile in einem Codeblock erscheinen konnte. Erstellt jetztyield
einen Ertragsausdruck. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Diese Änderung wurde vorgeschlagen , damit ein Benutzer Daten so an den Generator senden kann, wie er sie möglicherweise erhält. Um Daten zu senden, muss man sie etwas zuweisen können, und dafür funktioniert eine Anweisung einfach nicht.quelle
yield
ist wiereturn
- es gibt alles zurück, was Sie ihm sagen (als Generator). Der Unterschied besteht darin, dass beim nächsten Aufruf des Generators die Ausführung vom letzten Aufruf deryield
Anweisung beginnt . Im Gegensatz zur Rückgabe wird der Stapelrahmen nicht bereinigt, wenn eine Ausbeute auftritt. Die Steuerung wird jedoch zurück an den Aufrufer übertragen, sodass sein Status beim nächsten Aufruf der Funktion fortgesetzt wird.Im Fall Ihres Codes
get_child_candidates
verhält sich die Funktion wie ein Iterator, sodass beim Erweitern Ihrer Liste jeweils ein Element zur neuen Liste hinzugefügt wird.list.extend
ruft einen Iterator auf, bis er erschöpft ist. Im Fall des von Ihnen veröffentlichten Codebeispiels wäre es viel klarer, nur ein Tupel zurückzugeben und dieses an die Liste anzuhängen.quelle
Es gibt noch eine weitere Sache zu erwähnen: Eine Funktion, die ergibt, muss nicht wirklich beendet werden. Ich habe folgenden Code geschrieben:
Dann kann ich es in einem anderen Code wie diesem verwenden:
Es hilft wirklich, einige Probleme zu vereinfachen und erleichtert die Arbeit mit einigen Dingen.
quelle
Wenn Sie ein minimales Arbeitsbeispiel bevorzugen, meditieren Sie über diese interaktive Python-Sitzung:
quelle
TL; DR
An Stelle von:
mach das:
Immer wenn Sie eine Liste von Grund auf neu erstellen,
yield
stattdessen jedes Stück.Dies war mein erster "Aha" -Moment mit Ertrag.
yield
ist eine zuckerhaltige Art zu sagenGleiches Verhalten:
Anderes Verhalten:
Die Ausbeute erfolgt in einem Durchgang : Sie können nur einmal durchlaufen. Wenn eine Funktion eine Ausbeute enthält, nennen wir sie eine Generatorfunktion . Und ein Iterator ist das, was er zurückgibt. Diese Begriffe sind aufschlussreich. Wir verlieren die Bequemlichkeit eines Containers, gewinnen aber die Leistung einer Serie, die nach Bedarf berechnet wird und beliebig lang ist.
Die Ausbeute ist faul , sie verschiebt die Berechnung. Eine Funktion mit einem Ertrag wird beim Aufrufen überhaupt nicht ausgeführt. Es gibt ein Iteratorobjekt zurück , das sich merkt, wo es aufgehört hat. Jedes Mal, wenn Sie
next()
den Iterator aufrufen (dies geschieht in einer for-Schleife), wird die Ausführung nur wenige Zentimeter vor dem nächsten Ertrag ausgeführt.return
löst StopIteration aus und beendet die Serie (dies ist das natürliche Ende einer for-Schleife).Der Ertrag ist vielseitig . Daten müssen nicht alle zusammen gespeichert werden, sondern können einzeln zur Verfügung gestellt werden. Es kann unendlich sein.
Wenn Sie mehrere Durchgänge benötigen und die Serie nicht zu lang ist, rufen
list()
Sie einfach an:Geniale Wortwahl,
yield
da beide Bedeutungen zutreffen:... liefern die nächsten Daten in der Reihe.
... die CPU-Ausführung aufgeben, bis der Iterator voranschreitet.
quelle
Ausbeute gibt Ihnen einen Generator.
Wie Sie sehen können, wird im ersten Fall
foo
die gesamte Liste auf einmal gespeichert. Es ist keine große Sache für eine Liste mit 5 Elementen, aber was ist, wenn Sie eine Liste mit 5 Millionen wollen? Dies ist nicht nur ein großer Speicherfresser, es kostet auch viel Zeit, um es zum Zeitpunkt des Aufrufs der Funktion zu erstellen.Im zweiten Fall erhalten
bar
Sie nur einen Generator. Ein Generator ist iterierbar - das heißt, Sie können ihn in einerfor
Schleife usw. verwenden, aber auf jeden Wert kann nur einmal zugegriffen werden. Es werden auch nicht alle Werte gleichzeitig gespeichert. Das Generatorobjekt "merkt" sich, wo es sich beim letzten Aufruf in der Schleife befand. Wenn Sie also eine Iterable verwenden, um beispielsweise 50 Milliarden zu zählen, müssen Sie nicht alle bis 50 Milliarden zählen sofort und speichern Sie die 50 Milliarden Zahlen, um durchzuzählen.Auch dies ist ein ziemlich ausgeklügeltes Beispiel. Sie würden wahrscheinlich itertools verwenden, wenn Sie wirklich bis 50 Milliarden zählen wollten. :) :)
Dies ist der einfachste Anwendungsfall von Generatoren. Wie Sie sagten, kann es verwendet werden, um effiziente Permutationen zu schreiben, wobei Yield verwendet wird, um Dinge durch den Aufrufstapel zu schieben, anstatt eine Art Stapelvariable zu verwenden. Generatoren können auch für die spezielle Baumdurchquerung und für alle möglichen anderen Zwecke verwendet werden.
quelle
range
anstelle einer Liste auch ein Generator zurückgegeben, sodass Sie auch eine ähnliche Idee sehen, außer dass__repr__
/__str__
überschrieben werden, um in diesem Fall ein besseres Ergebnis zu erzielenrange(1, 10, 2)
.Es gibt einen Generator zurück. Ich bin mit Python nicht besonders vertraut, aber ich glaube, es ist das Gleiche wie die Iteratorblöcke von C #, wenn Sie mit diesen vertraut sind.
Die Schlüsselidee ist, dass der Compiler / Interpreter / was auch immer einige Tricks macht, damit der Aufrufer weiterhin next () aufrufen kann und weiterhin Werte zurückgibt - als ob die Generatormethode angehalten worden wäre . Offensichtlich können Sie eine Methode nicht wirklich "pausieren", daher erstellt der Compiler eine Zustandsmaschine, damit Sie sich daran erinnern können, wo Sie sich gerade befinden und wie die lokalen Variablen usw. aussehen. Dies ist viel einfacher als selbst einen Iterator zu schreiben.
quelle
Es gibt eine Art von Antwort, von der ich glaube, dass sie noch nicht gegeben wurde, unter den vielen großartigen Antworten, die den Umgang mit Generatoren beschreiben. Hier ist die Antwort auf die Programmiersprachtheorie:
Die
yield
Anweisung in Python gibt einen Generator zurück. Ein Generator in Python ist eine Funktion, die Fortsetzungen zurückgibt (und insbesondere eine Art Coroutine, aber Fortsetzungen stellen den allgemeineren Mechanismus dar, um zu verstehen, was vor sich geht).Fortsetzungen in der Theorie der Programmiersprachen sind eine viel grundlegendere Art der Berechnung, werden jedoch nicht oft verwendet, da sie äußerst schwer zu überlegen und auch sehr schwer zu implementieren sind. Die Vorstellung, was eine Fortsetzung ist, ist jedoch unkompliziert: Es ist der Zustand einer Berechnung, der noch nicht abgeschlossen ist. In diesem Zustand werden die aktuellen Werte von Variablen, die noch auszuführenden Operationen usw. gespeichert. Dann kann zu einem späteren Zeitpunkt im Programm die Fortsetzung aufgerufen werden, so dass die Variablen des Programms auf diesen Zustand zurückgesetzt werden und die gespeicherten Operationen ausgeführt werden.
Fortsetzungen in dieser allgemeineren Form können auf zwei Arten implementiert werden. Auf diese
call/cc
Weise wird der Stapel des Programms buchstäblich gespeichert, und wenn die Fortsetzung aufgerufen wird, wird der Stapel wiederhergestellt.Im Continuation Passing Style (CPS) sind Fortsetzungen nur normale Funktionen (nur in Sprachen, in denen Funktionen erstklassig sind), die der Programmierer explizit verwaltet und an Unterprogramme weitergibt. In diesem Stil wird der Programmstatus eher durch Verschlüsse (und die darin codierten Variablen) als durch Variablen dargestellt, die sich irgendwo auf dem Stapel befinden. Funktionen, die den Kontrollfluss verwalten, akzeptieren die Fortsetzung als Argumente (in einigen Variationen von CPS akzeptieren Funktionen möglicherweise mehrere Fortsetzungen) und manipulieren den Kontrollfluss, indem sie sie aufrufen, indem sie einfach aufgerufen und anschließend zurückgegeben werden. Ein sehr einfaches Beispiel für den Continuation-Passing-Stil lautet wie folgt:
In diesem (sehr vereinfachten) Beispiel speichert der Programmierer die Operation des tatsächlichen Schreibens der Datei in eine Fortsetzung (was möglicherweise eine sehr komplexe Operation mit vielen zu schreibenden Details sein kann) und übergibt diese Fortsetzung dann (dh als erste). Klassenschließung) an einen anderen Operator, der weitere Verarbeitungen vornimmt und diese dann bei Bedarf aufruft. (Ich verwende dieses Entwurfsmuster häufig in der eigentlichen GUI-Programmierung, entweder weil es mir Codezeilen spart oder, was noch wichtiger ist, um den Kontrollfluss nach dem Auslösen von GUI-Ereignissen zu verwalten.)
Der Rest dieses Beitrags wird ohne Verlust der Allgemeinheit Fortsetzungen als CPS konzipieren, da es verdammt viel einfacher zu verstehen und zu lesen ist.
Lassen Sie uns nun über Generatoren in Python sprechen. Generatoren sind ein spezifischer Subtyp der Fortsetzung. Während Fortsetzungen im Allgemeinen den Status einer Berechnung (dh den Aufrufstapel des Programms) speichern können , können Generatoren den Iterationsstatus nur über einen Iterator speichern . Diese Definition ist jedoch für bestimmte Anwendungsfälle von Generatoren leicht irreführend. Zum Beispiel:
Dies ist eindeutig eine vernünftige Iterierbarkeit, deren Verhalten genau definiert ist. Jedes Mal, wenn der Generator darüber iteriert, gibt er 4 zurück (und dies für immer). Aber es ist wahrscheinlich nicht der prototypische Typ von Iterable, der in den Sinn kommt, wenn man an Iteratoren denkt (dh
for x in collection: do_something(x)
). Dieses Beispiel zeigt die Leistung von Generatoren: Wenn etwas ein Iterator ist, kann ein Generator den Status seiner Iteration speichern.Um es noch einmal zu wiederholen: Fortsetzungen können den Status des Stacks eines Programms speichern, und Generatoren können den Status der Iteration speichern. Dies bedeutet, dass Fortsetzungen viel leistungsfähiger sind als Generatoren, aber auch, dass Generatoren viel, viel einfacher sind. Sie sind für den Sprachdesigner einfacher zu implementieren und für den Programmierer einfacher zu verwenden (wenn Sie etwas Zeit zum Brennen haben, versuchen Sie, diese Seite über Fortsetzungen und Aufruf / cc zu lesen und zu verstehen ).
Sie können Generatoren jedoch leicht als einfachen, spezifischen Fall eines Continuation-Passing-Stils implementieren (und konzipieren):
Bei jedem
yield
Aufruf wird die Funktion angewiesen, eine Fortsetzung zurückzugeben. Wenn die Funktion erneut aufgerufen wird, beginnt sie dort, wo sie aufgehört hat. Im Pseudo-Pseudocode (dh nicht im Pseudocode, aber nicht im Code)next
lautet die Methode des Generators im Wesentlichen wie folgt:wobei das
yield
Schlüsselwort tatsächlich syntaktischer Zucker für die eigentliche Generatorfunktion ist, im Grunde so etwas wie:Denken Sie daran, dass dies nur ein Pseudocode ist und die tatsächliche Implementierung von Generatoren in Python komplexer ist. Versuchen Sie jedoch, um zu verstehen, was vor sich geht, den Continuation-Passing-Stil zu verwenden, um Generatorobjekte ohne Verwendung des
yield
Schlüsselworts zu implementieren .quelle
Hier ist ein Beispiel im Klartext. Ich werde eine Entsprechung zwischen menschlichen Konzepten auf hoher Ebene und Python-Konzepten auf niedriger Ebene bereitstellen.
Ich möchte eine Folge von Zahlen bearbeiten, aber ich möchte mich nicht mit der Erstellung dieser Folge beschäftigen, sondern mich nur auf die Operation konzentrieren, die ich ausführen möchte. Also mache ich folgendes:
Dieser Schritt entspricht der
def
Eingabe der Generatorfunktion, dh der Funktion, die a enthältyield
.Dieser Schritt entspricht dem Aufruf der Generatorfunktion, die ein Generatorobjekt zurückgibt. Beachten Sie, dass Sie mir noch keine Zahlen mitteilen. Sie greifen einfach nach Papier und Bleistift.
Dieser Schritt entspricht dem Aufruf
.next()
des Generatorobjekts.Dieser Schritt entspricht dem Generatorobjekt, das seinen Job beendet und eine
StopIteration
Ausnahme auslöst. Die Generatorfunktion muss die Ausnahme nicht auslösen. Es wird automatisch ausgelöst, wenn die Funktion endet oder a ausgibtreturn
.Dies ist, was ein Generator tut (eine Funktion, die a enthält
yield
); Es wird ausgeführt, pausiert, wann immer es ausgeführt wirdyield
, und wenn es nach einem.next()
Wert gefragt wird, wird es an dem Punkt fortgesetzt, an dem es zuletzt war. Es passt vom Design her perfekt zum Iterator-Protokoll von Python, das beschreibt, wie Werte nacheinander angefordert werden.Der bekannteste Benutzer des Iterator-Protokolls ist der
for
Befehl in Python. Also, wann immer Sie eine:Es spielt keine Rolle, ob
sequence
es sich um eine Liste, eine Zeichenfolge, ein Wörterbuch oder ein Generatorobjekt handelt , wie oben beschrieben. Das Ergebnis ist das gleiche: Sie lesen Elemente einzeln aus einer Sequenz.Beachten Sie, dass das
def
Eingeben einer Funktion, die einyield
Schlüsselwort enthält, nicht die einzige Möglichkeit ist, einen Generator zu erstellen. Es ist nur der einfachste Weg, einen zu erstellen.Weitere Informationen zu Iteratortypen , der Yield-Anweisung und den Generatoren finden Sie in der Python-Dokumentation.
quelle
Während viele Antworten zeigen, warum Sie einen verwenden würden
yield
, um einen Generator zu erstellen, gibt es mehr Verwendungsmöglichkeiten füryield
. Es ist ziemlich einfach, eine Coroutine zu erstellen, die die Weitergabe von Informationen zwischen zwei Codeblöcken ermöglicht. Ich werde keines der guten Beispiele wiederholen, die bereitsyield
zum Erstellen eines Generators verwendet wurden.Um zu verstehen, was a
yield
im folgenden Code tut, können Sie den Zyklus mit Ihrem Finger durch jeden Code mit a verfolgenyield
. Jedes Mal, wenn Ihr Finger auf den Finger trifftyield
, müssen Sie warten, bis einnext
oder einsend
eingegeben wird. Wenn anext
aufgerufen wird, verfolgen Sie den Code, bis Sie aufyield
... klicken. Der Code rechts vonyield
wird ausgewertet und an den Anrufer zurückgegeben. Dann warten Sie. Wennnext
es erneut aufgerufen wird, führen Sie eine weitere Schleife durch den Code durch. Sie werden jedoch feststellen, dass in einer Coroutineyield
auch mit einemsend
… verwendet werden kann, das einen Wert vom Aufrufer an die Yielding-Funktion sendet . Wenn einsend
gegeben ist, dannyield
Empfängt den gesendeten Wert und spuckt ihn auf der linken Seite aus. Dann wird die Verfolgung durch den Code fortgesetzt, bis Sie aufgerufen wurde).yield
wieder (Rückgabe des Wertes am Ende, als obnext
Zum Beispiel:
quelle
Es gibt eine andere
yield
Verwendung und Bedeutung (seit Python 3.3):Aus PEP 380 - Syntax zum Delegieren an einen Subgenerator :
Darüber hinaus wird dies (seit Python 3.5) einführen:
um zu vermeiden, dass Coroutinen mit einem normalen Generator verwechselt werden (wird heute
yield
in beiden verwendet).quelle
Alles gute Antworten, allerdings etwas schwierig für Neulinge.
Ich gehe davon aus, dass Sie die
return
Aussage gelernt haben .Als Analogie
return
undyield
sind Zwillinge.return
bedeutet "Rückkehr und Stopp", während "Rendite" "Rückkehr, aber Fortsetzung" bedeutet.Starte es:
Sie erhalten nur eine einzige Nummer und keine Liste.
return
Erlaubt dir niemals, dich glücklich durchzusetzen, implementiert nur einmal und beendet.Ersetzen
return
durchyield
:Jetzt gewinnen Sie, um alle Zahlen zu erhalten.
Wenn Sie vergleichen,
return
welche einmal ausgeführt und angehalten werden, werdenyield
die von Ihnen geplanten Zeiten ausgeführt. Sie könnenreturn
alsreturn one of them
undyield
als interpretierenreturn all of them
. Dies nennt maniterable
.Es geht um den Kern
yield
.Der Unterschied zwischen einer Listenausgabe
return
und der Objektausgabeyield
ist:Sie erhalten immer [0, 1, 2] von einem Listenobjekt, können diese jedoch nur einmal aus der 'Objektausgabe
yield
' abrufen . Es hat also ein neuesgenerator
Namensobjekt, wie in angezeigtOut[11]: <generator object num_list at 0x10327c990>
.Abschließend als Metapher, um es zu verstehen:
return
undyield
sind Zwillingelist
undgenerator
sind Zwillingequelle
yield
. Dies ist wichtig, denke ich, und sollte zum Ausdruck gebracht werden.Hier sind einige Python-Beispiele, wie Generatoren tatsächlich implementiert werden, als ob Python keinen syntaktischen Zucker für sie bereitstellen würde:
Als Python-Generator:
Verwendung von lexikalischen Verschlüssen anstelle von Generatoren
Verwenden von Objektschließungen anstelle von Generatoren (weil ClosuresAndObjectsAreEquivalent )
quelle
Ich wollte "Seite 19 von Beazleys 'Python: Essential Reference' lesen, um eine schnelle Beschreibung der Generatoren zu erhalten", aber so viele andere haben bereits gute Beschreibungen veröffentlicht.
Beachten Sie auch, dass
yield
in Coroutinen die doppelte Verwendung in Generatorfunktionen verwendet werden kann. Obwohl es nicht die gleiche Verwendung wie Ihr Code-Snippet ist,(yield)
kann es als Ausdruck in einer Funktion verwendet werden. Wenn ein Aufrufer mithilfe dersend()
Methode einen Wert an die Methode sendet , wird die Coroutine ausgeführt, bis die nächste(yield)
Anweisung gefunden wird.Generatoren und Coroutinen sind eine coole Möglichkeit, Anwendungen vom Typ Datenfluss einzurichten. Ich dachte, es lohnt sich, über die andere Verwendung der
yield
Anweisung in Funktionen Bescheid zu wissen .quelle
Aus programmtechnischer Sicht sind die Iteratoren als Thunks implementiert .
Um Iteratoren, Generatoren und Thread-Pools für die gleichzeitige Ausführung usw. als Thunks (auch als anonyme Funktionen bezeichnet) zu implementieren, werden Nachrichten verwendet, die an ein Abschlussobjekt gesendet werden, das einen Dispatcher hat, und der Dispatcher antwortet auf "Nachrichten".
http://en.wikipedia.org/wiki/Message_passing
" next " ist eine Nachricht, die an einen Abschluss gesendet wird und vom " iter " erstellt wird " -Aufruf erstellt wurde.
Es gibt viele Möglichkeiten, diese Berechnung zu implementieren. Ich habe eine Mutation verwendet, aber es ist einfach, dies ohne Mutation zu tun, indem der aktuelle Wert und der nächste Ertrag zurückgegeben werden.
Hier ist eine Demonstration, die die Struktur von R6RS verwendet, aber die Semantik ist absolut identisch mit der von Python. Es ist dasselbe Berechnungsmodell, und nur eine Änderung der Syntax ist erforderlich, um es in Python neu zu schreiben.
quelle
Hier ist ein einfaches Beispiel:
Ausgabe:
Ich bin kein Python-Entwickler, aber es sieht für mich so aus
yield
ob die Position des Programmflusses und die nächste Schleife von der Position "Yield" ausgehen. Es scheint, als würde es an dieser Position warten und kurz davor einen Wert nach draußen zurückgeben und das nächste Mal weiterarbeiten.Es scheint eine interessante und nette Fähigkeit zu sein: D.
quelle
Hier ist ein mentales Bild von dem, was
yield
tut.Ich stelle mir einen Thread gerne als Stack vor (auch wenn er nicht so implementiert ist).
Wenn eine normale Funktion aufgerufen wird, legt sie ihre lokalen Variablen auf den Stapel, führt einige Berechnungen durch, löscht dann den Stapel und kehrt zurück. Die Werte der lokalen Variablen werden nie wieder angezeigt.
yield
Wenn bei einer Funktion die Ausführung ihres Codes beginnt (dh nachdem die Funktion aufgerufen wurde und ein Generatorobjekt zurückgegeben wird, dessennext()
Methode dann aufgerufen wird), legt sie ihre lokalen Variablen auf ähnliche Weise auf den Stapel und berechnet eine Weile. Wenn es jedoch auf dieyield
Anweisung trifft , bevor es seinen Teil des Stapels löscht und zurückgibt, erstellt es eine Momentaufnahme seiner lokalen Variablen und speichert sie im Generatorobjekt. Es schreibt auch die Stelle, an der es sich gerade befindet, in seinen Code (dh die bestimmteyield
Anweisung).Es ist also eine Art eingefrorene Funktion, an der der Generator festhält.
Wenn
next()
es später aufgerufen wird, ruft es die Habseligkeiten der Funktion auf dem Stapel ab und animiert sie erneut. Die Funktion berechnet weiterhin dort, wo sie aufgehört hat, ohne zu wissen, dass sie gerade eine Ewigkeit im Kühlhaus verbracht hat.Vergleichen Sie die folgenden Beispiele:
Wenn wir die zweite Funktion aufrufen, verhält sie sich ganz anders als die erste. Die
yield
Aussage ist möglicherweise nicht erreichbar, aber wenn sie irgendwo vorhanden ist, ändert sie die Art des Umgangs mit uns.Beim Aufrufen
yielderFunction()
wird der Code nicht ausgeführt, sondern aus dem Code wird ein Generator erstellt. (Vielleicht ist es eine gute Idee, solche Dinge mit demyielder
Präfix für Lesbarkeit zu benennen .)Die
gi_code
undgi_frame
Felder sind , wo der gefrorene Zustand gelagert wird. Wenndir(..)
wir sie mit untersuchen , können wir bestätigen, dass unser oben genanntes mentales Modell glaubwürdig ist.quelle
Wie jede Antwort andeutet,
yield
wird zum Erstellen eines Sequenzgenerators verwendet. Es wird zum dynamischen Generieren einer Sequenz verwendet. Wenn Sie beispielsweise eine Datei zeilenweise in einem Netzwerk lesen, können Sie die folgendeyield
Funktion verwenden:Sie können es in Ihrem Code wie folgt verwenden:
Execution Control Transfer gotcha
Die Ausführungssteuerung wird von getNextLines () in die
for
Schleife übertragen, wenn Yield ausgeführt wird. Daher beginnt die Ausführung jedes Mal, wenn getNextLines () aufgerufen wird, an dem Punkt, an dem sie das letzte Mal angehalten wurde.Kurz gesagt, eine Funktion mit dem folgenden Code
wird gedruckt
quelle
Ein einfaches Beispiel, um zu verstehen, was es ist:
yield
Die Ausgabe ist:
quelle
print(i, end=' ')
? Andernfalls glaube ich, dass das Standardverhalten jede Zahl in eine neue Zeile setzen würde(Meine folgende Antwort spricht nur aus der Perspektive der Verwendung des Python-Generators, nicht aus der zugrunde liegenden Implementierung des Generatormechanismus bezieht sich , der einige Tricks der Stapel- und Heap-Manipulation beinhaltet.)
Wenn
yield
anstelle einerreturn
in einer Python-Funktion verwendet wird, wird diese Funktion in etwas Besonderes umgewandelt, das als bezeichnet wirdgenerator function
. Diese Funktion gibt ein Objekt vomgenerator
Typ zurück. Dasyield
Schlüsselwort ist ein Flag, mit dem der Python-Compiler benachrichtigt wird, diese Funktion speziell zu behandeln. Normale Funktionen werden beendet, sobald ein Wert zurückgegeben wird. Mit Hilfe des Compilers kann die Generatorfunktion jedoch als wiederaufnehmbar angesehen werden. Das heißt, der Ausführungskontext wird wiederhergestellt und die Ausführung wird vom letzten Lauf fortgesetzt. Bis Sie return explizit aufrufen, wodurch eineStopIteration
Ausnahme ausgelöst wird (die auch Teil des Iteratorprotokolls ist) oder das Ende der Funktion erreichen. Ich habe viele Referenzen übergenerator
aber dies einvon derfunctional programming perspective
ist am verdaulichsten.(Jetzt möchte ich über die Gründe sprechen, die dahinter stehen
generator
, und dieiterator
auf meinem eigenen Verständnis beruhen. Ich hoffe, dies kann Ihnen helfen, die wesentliche Motivation von Iterator und Generator zu erfassen . Ein solches Konzept wird auch in anderen Sprachen wie C # angezeigt.)Soweit ich weiß, speichern wir die Daten, wenn wir eine Reihe von Daten verarbeiten möchten, normalerweise zuerst irgendwo und verarbeiten sie dann einzeln. Dieser naive Ansatz ist jedoch problematisch. Wenn das Datenvolumen sehr groß ist, ist es teuer, es vorher als Ganzes zu speichern. Also, anstatt das sich
data
selbst direkt zu speichern, warum nicht eine Artmetadata
indirekt speichern , dhthe logic how the data is computed
.Es gibt zwei Ansätze, um solche Metadaten zu verpacken.
as a class
. Dies ist der sogenannte,iterator
der das Iteratorprotokoll implementiert (dh die__next__()
, und__iter__()
Methoden). Dies ist auch das häufig verwendete Iterator-Entwurfsmuster .as a function
. Dies ist das sogenanntegenerator function
. Unter der Haube ist der zurückgegebene Iterator jedochgenerator object
immer noch vorhanden,IS-A
da er auch das Iteratorprotokoll implementiert.In beiden Fällen wird ein Iterator erstellt, dh ein Objekt, das Ihnen die gewünschten Daten liefern kann. Der OO-Ansatz kann etwas komplex sein. Wie auch immer, welche Sie verwenden, liegt bei Ihnen.
quelle
Zusammenfassend
yield
wandelt die Anweisung Ihre Funktion in eine Factory um, die ein spezielles Objekt namens a erzeugt,generator
das den Körper Ihrer ursprünglichen Funktion umschließt. Wenn dasgenerator
iteriert wird, führt es Ihre Funktion aus, bis es das nächste erreicht, setztyield
dann die Ausführung aus und wertet den übergebenen Wert ausyield
. Dieser Vorgang wird bei jeder Iteration wiederholt, bis der Ausführungspfad die Funktion verlässt. Zum Beispiel,einfach ausgibt
Die Leistung kommt von der Verwendung des Generators mit einer Schleife, die eine Sequenz berechnet. Der Generator führt die Schleife jedes Mal aus, um das nächste Ergebnis der Berechnung zu "liefern". Auf diese Weise berechnet er eine Liste im laufenden Betrieb, wobei der Vorteil der Speicher ist für besonders große Berechnungen gespeichert
Angenommen, Sie möchten eine eigene
range
Funktion erstellen , die einen iterierbaren Zahlenbereich erzeugt. Sie können dies folgendermaßen tun:und benutze es so;
Das ist aber ineffizient, weil
Glücklicherweise waren Guido und sein Team großzügig genug, um Generatoren zu entwickeln, damit wir dies einfach tun konnten.
Bei jeder Iteration führt eine Funktion auf dem aufgerufenen Generator
next()
die Funktion aus, bis sie entweder eine 'Yield'-Anweisung erreicht, in der sie stoppt und den Wert' ergibt 'oder das Ende der Funktion erreicht. In diesem Fall wird beim ersten Aufrufnext()
die Yield-Anweisung und Yield 'n' ausgeführt. Beim nächsten Aufruf wird die Inkrement-Anweisung ausgeführt, zum 'while' zurückgesprungen, ausgewertet und bei true gestoppt und gestoppt Geben Sie 'n' wieder ein, so wird es fortgesetzt, bis die while-Bedingung false zurückgibt und der Generator zum Ende der Funktion springt.quelle
Ertrag ist ein Objekt
EIN
return
in einer Funktion gibt einen einzelnen Wert zurück.Wenn eine Funktion eine große Anzahl von Werten zurückgeben soll , verwenden Sie
yield
.Noch wichtiger
yield
ist eine Barriere .Das heißt, es wird der Code in Ihrer Funktion von Anfang an ausgeführt, bis er trifft
yield
. Dann wird der erste Wert der Schleife zurückgegeben.Dann führt jeder zweite Aufruf die Schleife, die Sie in die Funktion geschrieben haben, noch einmal aus und gibt den nächsten Wert zurück, bis kein Wert mehr zurückzugeben ist.
quelle
Viele Menschen verwenden
return
eher alsyield
, aber in einigen Fällenyield
kann es effizienter und einfacher sein, damit zu arbeiten.Hier ist ein Beispiel, das
yield
definitiv am besten für:Beide Funktionen machen dasselbe,
yield
verwenden jedoch drei statt fünf Zeilen und haben eine Variable weniger, über die Sie sich Sorgen machen müssen.Wie Sie sehen, machen beide Funktionen dasselbe. Der einzige Unterschied besteht darin,
return_dates()
eine Liste undyield_dates()
einen Generator anzugeben.Ein Beispiel aus dem wirklichen Leben wäre so etwas wie das zeilenweise Lesen einer Datei oder wenn Sie nur einen Generator erstellen möchten.
quelle
yield
ist wie ein Rückgabeelement für eine Funktion. Der Unterschied besteht darin, dass dasyield
Element eine Funktion in einen Generator verwandelt. Ein Generator verhält sich wie eine Funktion, bis etwas "nachgegeben" wird. Der Generator stoppt bis zum nächsten Aufruf und fährt genau an dem Punkt fort, an dem er gestartet wurde. Sie können eine Folge aller 'ergebenen' Werte in einem erhalten, indem Sie aufrufenlist(generator())
.quelle
Das
yield
Schlüsselwort sammelt einfach wiederkehrende Ergebnisse. Denken Sie anyield
wiereturn +=
quelle
Hier ist ein einfacher
yield
Ansatz zur Berechnung der Fibonacci-Reihe, der erklärt wird:Wenn Sie dies in Ihre REPL eingeben und dann versuchen, es aufzurufen, erhalten Sie ein mysteriöses Ergebnis:
Dies liegt daran
yield
, dass Python signalisiert, dass Sie einen Generator erstellen möchten, dh ein Objekt, das bei Bedarf Werte generiert.Wie generieren Sie diese Werte? Dies kann entweder direkt über die integrierte Funktion erfolgen
next
oder indirekt durch Einspeisung in ein Konstrukt erfolgen, das Werte verbraucht.Mit der integrierten
next()
Funktion rufen Sie.next
/ direkt auf__next__
und zwingen den Generator, einen Wert zu erzeugen:Indirekt "verbrauchen" Sie den Generator , wenn Sie
fib
einerfor
Schleife, einemlist
Initialisierer, einemtuple
Initialisierer oder etwas anderem, das ein Objekt erwartet, das Werte generiert / erzeugt, etwas bereitstellen , bis keine Werte mehr von ihm erzeugt werden können (und es zurückkehrt). ::Ebenso mit einem
tuple
Initialisierer:Ein Generator unterscheidet sich von einer Funktion dadurch, dass er faul ist. Dies wird erreicht, indem der lokale Status beibehalten wird und Sie jederzeit fortfahren können.
Wenn Sie es zum ersten Mal aufrufen,
fib
indem Sie es aufrufen:Python kompiliert die Funktion, trifft auf das
yield
Schlüsselwort und gibt einfach ein Generatorobjekt an Sie zurück. Es scheint nicht sehr hilfreich zu sein.Wenn Sie dann anfordern, dass es direkt oder indirekt den ersten Wert generiert, führt es alle gefundenen Anweisungen aus, bis es auf a trifft
yield
, und gibt dann den Wert zurück, den Sie angegeben haben,yield
und pausiert. Verwenden Sie für ein Beispiel, das dies besser demonstriert, einigeprint
Aufrufe (ersetzen Sie sie durchprint "text"
if unter Python 2):Geben Sie nun die REPL ein:
Sie haben jetzt ein Generatorobjekt, das auf einen Befehl wartet, damit es einen Wert generiert. Verwenden Sie
next
und sehen Sie, was gedruckt wird:Die nicht zitierten Ergebnisse sind das, was gedruckt wird. Das angegebene Ergebnis ist das, von dem zurückgegeben wird
yield
. Rufen Sienext
jetzt noch einmal an:Der Generator merkt sich, dass er angehalten wurde,
yield value
und fährt von dort fort. Die nächste Nachricht wird gedruckt und die Suche nach deryield
Anweisung, die angehalten werden soll, wird erneut ausgeführt (aufgrund derwhile
Schleife).quelle