it-swarm-eu.dev

Was macht das Keyword "Ertrag"?

Was ist die Verwendung des yield-Schlüsselworts in Python? Was tut es?

Ich versuche beispielsweise, diesen Code zu verstehen1:

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

Und das ist der Anrufer:

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

Was passiert, wenn die Methode _get_child_candidates aufgerufen wird? Wird eine Liste zurückgegeben? Ein einzelnes Element? Heißt es wieder? Wann werden nachfolgende Anrufe aufhören?


1. Der Code stammt von Jochen Schulz (jrschulz), der eine großartige Python-Bibliothek für metrische Bereiche erstellt hat. Dies ist der Link zur vollständigen Quelle: Modul mspace .

8943
Alex. S.

Um zu verstehen, was yield tut, müssen Sie verstehen, was Generatoren sind. Und vor Generatoren kommen iterables.

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

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

Alles, was Sie mit "for... in..." verwenden können, ist eine Iteration. lists, strings, Dateien ...

Diese Iterables sind praktisch, weil Sie sie so oft lesen können, wie Sie möchten, aber Sie speichern alle Werte im Speicher. Dies ist nicht immer das, was Sie wollen, wenn Sie viele Werte haben.

Generatoren

Generatoren sind Iteratoren, eine Art iterierbare Sie können nur einmal durchlaufen. Generatoren speichern nicht alle Werte im Speicher, sie erzeugen die Werte spontan:

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

Es ist genau dasselbe, außer dass Sie () anstelle von [] verwendet haben. ABER, Sie können nicht _ for i in mygenerator ein zweites Mal ausführen, da Generatoren nur einmal verwendet werden können: Sie berechnen 0, vergessen sie dann und berechnen 1, und beenden 4 nacheinander.

Ausbeute

yield ist ein Schlüsselwort, das wie return verwendet wird, mit der Ausnahme, 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

Dies ist ein unbrauchbares Beispiel, aber es ist praktisch, wenn Sie wissen, dass Ihre Funktion eine riesige Menge von Werten zurückgibt, die Sie nur einmal lesen müssen.

Um yield zu beherrschen, müssen Sie verstehen, dass wenn Sie die Funktion aufrufen, der Code, den Sie in den Funktionsrumpf geschrieben haben, nicht ausgeführt wird. Die Funktion gibt nur das Generatorobjekt zurück, dies ist etwas knifflig :-)

Ihr Code wird dann an jeder Stelle fortgesetzt, an der er aufgehört hat, wenn for den Generator verwendet.

Nun der schwierige Teil:

Wenn for das von Ihrer Funktion erstellte Generatorobjekt zum ersten Mal aufruft, wird der Code in Ihrer Funktion vom Anfang bis zum Aufruf von yield ausgeführt. Dann wird der erste Wert der Schleife zurückgegeben. Dann führt jeder andere Aufruf die Schleife, die Sie in die Funktion geschrieben haben, noch einmal aus und gibt den nächsten Wert zurück, bis es keinen Wert gibt, der zurückgegeben werden soll.

Der Generator wird als leer betrachtet, sobald die Funktion ausgeführt wird, trifft jedoch nicht mehr auf yield. Dies kann daran liegen, dass die Schleife beendet wurde oder Sie einen "if/else" nicht mehr befriedigen.


Ihr Code wurde 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 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 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 candidates 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 durchläuft eine Liste, die Liste wird jedoch erweitert, während die Schleife durchlaufen wird :-) Es ist eine prägnante Methode, all diese verschachtelten Daten durchzugehen, auch wenn sie etwas gefährlich ist, da Sie mit einer Endlosschleife enden können. In diesem Fall erschöpft candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) alle Werte des Generators, aber while erstellt ständig neue Generatorobjekte, die sich von den vorherigen unterscheiden, da sie nicht auf denselben Knoten angewendet werden.

  • Die extend()-Methode ist eine Listenobjektmethode, die eine Iteration 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:

  1. Sie müssen die Werte nicht zweimal lesen.
  2. Möglicherweise haben Sie viele Kinder und möchten nicht, dass sie alle im Speicher gespeichert werden.

Und es funktioniert, weil Python sich nicht darum kümmert, ob das Argument einer Methode eine Liste ist oder nicht. Python erwartet iterable, also funktioniert es mit Strings, Listen, Tupeln und Generatoren! Dies wird als Duck-Typing 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:

Die Erschöpfung eines Generators kontrollieren

>>> 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 die Funktionenprint(corner_street_atm.__next__()) oder print(next(corner_street_atm)).

Dies kann für verschiedene Zwecke nützlich sein, beispielsweise für die Steuerung des Zugriffs auf eine Ressource.

Itertools, dein bester Freund

Das itertools-Modul enthält spezielle Funktionen zur Bearbeitung von Iterablen. Wollten Sie schon immer einen Generator duplizieren? Zwei Generatoren ketten? Werte in einer verschachtelten Liste mit einem Einzeiler gruppieren? Map / Zip ohne eine weitere Liste zu erstellen?

Dann einfach import itertools.

Ein Beispiel? Lassen Sie uns die möglichen Reihenfolge der Ankunft für ein Vierpferderennen sehen:

>>> 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 iterable (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 iterierbare Elemente durchlaufen können.

In diesem Artikel erfahren Sie mehr darüber, wie wie for-Schleifen funktionieren .

13125
e-satis

Abkürzung zu Grokkingyield

Wenn Sie eine Funktion mit yield-Anweisungen sehen, wenden Sie diesen einfachen Trick an, um zu verstehen, was passieren wird:

  1. Fügen Sie am Anfang der Funktion eine Zeile result = [] ein.
  2. Ersetzen Sie jeden yield expr durch result.append(expr).
  3. Fügen Sie am Ende der Funktion eine Zeile return result ein.
  4. Yay - keine yield-Anweisungen mehr! Code lesen und herausfinden.
  5. Funktion mit der ursprünglichen Definition vergleichen.

Dieser Trick vermittelt zwar eine Vorstellung von der Logik hinter der Funktion, aber was mit yield tatsächlich passiert, unterscheidet sich erheblich von dem, was in dem auf Listen basierenden Ansatz geschieht. In vielen Fällen wird der Ertragsansatz viel speichereffizienter und auch schneller sein. In anderen Fällen bleibt der Trick in einer Endlosschleife stecken, auch wenn die ursprüngliche Funktion einwandfrei funktioniert. Lesen Sie weiter, um mehr zu erfahren ...

Verwechseln Sie Ihre Iterables, Iteratoren und Generatoren nicht

Zuerst das iterator-Protokoll - wenn Sie schreiben

for x in mylist:
    ...loop body...

Python führt die folgenden zwei Schritte aus:

  1. Ruft einen Iterator für mylist ab:

    Aufruf iter(mylist) -> dies gibt ein Objekt mit einer next()-Methode (oder __next__() in Python 3) zurück.

    [Dies ist der Schritt, von dem die meisten Leute vergessen, Ihnen etwas zu sagen]

  2. Verwendet den Iterator, um Elemente zu durchlaufen:

    Rufen Sie weiterhin die next()-Methode auf dem von Schritt 1 zurückgegebenen Iterator auf. Der Rückgabewert von next() wird x zugewiesen, und der Schleifenkörper wird ausgeführt. Wenn eine Ausnahme StopIteration innerhalb von next() ausgelöst wird, bedeutet dies, dass der Iterator keine weiteren Werte enthält und die Schleife verlassen wird.

Die Wahrheit ist, dass Python die oben genannten zwei Schritte immer dann ausführt, wenn er den Inhalt eines Objekts Schleife über möchte - es könnte sich also um eine for-Schleife handeln, aber es könnte auch ein Code wie otherlist.extend(mylist) sein (wobei otherlist eine Python-Liste ist ).

Hier ist mylist ein iterable, da 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 einer next()-Methode. Es ist möglich, __iter__() und next() in derselben Klasse zu implementieren und __iter__()self zurückzugeben. Dies funktioniert in einfachen Fällen, aber nicht, wenn zwei Iteratoren gleichzeitig über dasselbe Objekt laufen sollen.

Das ist das Iterator-Protokoll. Viele Objekte implementieren dieses Protokoll:

  1. Eingebaute Listen, Wörterbücher, Tupel, Sets, Dateien.
  2. Benutzerdefinierte Klassen, die __iter__() implementieren.
  3. Generatoren.

Beachten Sie, dass eine for-Schleife nicht weiß, um was für ein Objekt es sich handelt - sie folgt einfach dem Iterator-Protokoll und ist froh, ein Element nach dem anderen zu erhalten, während es next() aufruft. Integrierte Listen geben ihre Elemente nacheinander zurück, Wörterbücher geben die Schlüssel nacheinander zurück, Dateien geben die Zeilen nacheinander usw. zurück. Und Generatoren kehren zurück ... na gut yield kommt herein:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Wenn Sie statt yield-Anweisungen drei return-Anweisungen in f123() hätten, würde nur die erste ausgeführt und die Funktion beendet. Aber f123() ist keine gewöhnliche Funktion. Wenn f123() aufgerufen wird, gibt nicht einen der Werte in den Yield-Anweisungen zurück! Es gibt ein Generatorobjekt zurück. Die Funktion wird auch nicht wirklich beendet - sie wechselt in den Suspend-Zustand. Wenn die for-Schleife versucht, das Generatorobjekt zu durchlaufen, wird die Funktion in der nächsten Zeile nach der yield, aus der sie zuvor zurückgegeben wurde, aus dem angehaltenen Zustand wieder aufgenommen, führt die nächste Codezeile aus, in diesem Fall eine yield-Anweisung, und gibt diese als zurück der nächste Punkt Dies geschieht solange, bis die Funktion beendet ist. An diesem Punkt hebt der Generator StopIteration an und die Schleife wird beendet. 

Das Generatorobjekt ist also gewissermaßen ein Adapter - an einem Ende weist es das Iteratorprotokoll auf, indem es __iter__()- und next()-Methoden verfügbar macht, um die for-Schleife glücklich zu machen. Am anderen Ende führt es die Funktion jedoch gerade genug aus, um den nächsten Wert herauszuholen, und versetzt ihn in den Suspend-Modus.

Warum Generatoren verwenden?

Normalerweise können Sie Code schreiben, der keine Generatoren verwendet, aber dieselbe Logik implementiert. Eine Option ist die Verwendung der temporären Liste "Trick", die ich zuvor erwähnt habe. Das wird nicht in allen Fällen funktionieren, z. Wenn Sie über unendliche Schleifen verfügen, kann der Speicher bei einer sehr langen Liste ineffizient genutzt werden. Der andere Ansatz besteht darin, eine neue iterable-Klasse SomethingIter zu implementieren, die den Status in Instanzmitgliedern beibehält und den nächsten logischen Schritt in ihrer Methode next() (oder __next__() in Python 3) ausführt. Je nach Logik kann der Code in der next()-Methode sehr komplex aussehen und anfällig für Fehler sein. Hier bieten Generatoren eine saubere und einfache Lösung.

1751
user28409

Denk darüber so:

Ein Iterator ist nur ein ausgefallener Begriff für ein Objekt, das eine next() -Methode hat. Eine Yield-Ed-Funktion sieht also so aus:

Originalfassung:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

Dies ist im Grunde das, was der Python -Interpreter mit dem obigen Code macht:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

Um mehr über die Ereignisse hinter den Kulissen zu erfahren, kann die for -Schleife folgendermaßen umgeschrieben werden:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Macht das mehr Sinn oder verwirrt es Sie nur mehr? :)

Ich sollte beachten, dass dies zu Illustrationszwecken eine übermäßige Vereinfachung ist. :)

493
Jason Baker

Das Schlüsselwort yield reduziert sich auf zwei einfache Fakten:

  1. Wenn der Compiler das yield-Schlüsselwort irgendwo in einer Funktion erkennt, wird diese Funktion nicht mehr über die return-Anweisung zurückgegeben. Statt gibt sofort ein lazy-Objekt "ausstehende Liste" zurück, das als Generator bezeichnet wird
  2. Ein Generator ist iterierbar. Was ist ein iterable? Es ist alles wie eine list oder set oder range oder dict-view mit einem eingebauten -Protokoll, um jedes Element in einer bestimmten Reihenfolge aufzurufen.

Kurz gesagt: Ein Generator ist eine faule, inkrementell anstehende Liste und yield -Anweisungen ermöglichen die Verwendung der Funktionsnotation zum Programmieren der Listenwerte, die der Generator inkrementell ausspucken sollte.

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

Beispiel

Definieren wir eine Funktion makeRange, die genau wie Pythons range ist. Aufruf von makeRange(n) RETURNS EINEN GENERATOR:

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

Um zu erzwingen, dass der Generator die anstehenden Werte sofort zurückgibt, können Sie ihn an list() übergeben (genau wie Sie es auch iterierbar machen könnten):

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

Beispiel vergleichen mit "nur eine Liste zurückgeben"

Das obige Beispiel kann als bloßes Erstellen einer Liste betrachtet werden, an die Sie anhängen und zurückkehren:

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

Es gibt jedoch einen wesentlichen Unterschied. siehe den letzten Abschnitt.


Wie können Sie Generatoren verwenden?

Ein iterierbares Element ist der letzte Teil eines Listenverständnisses. Alle Generatoren sind iterierbar. Daher werden sie häufig wie folgt verwendet:

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

Um ein besseres Gefühl für Generatoren zu erhalten, können Sie mit dem Modul itertools herumspielen (verwenden Sie bei Bedarf unbedingt chain.from_iterable anstelle von chain). Beispielsweise können Sie Generatoren verwenden, um unendlich lange Lazy-Listen wie itertools.count() zu implementieren. Sie können Ihre eigene def enumerate(iterable): Zip(count(), iterable) implementieren oder alternativ das Schlüsselwort yield in einer while-Schleife verwenden.

Bitte beachten Sie: Generatoren können tatsächlich für viele weitere Dinge verwendet werden, z. B. Implementieren von Coroutinen oder nicht deterministische Programmierung oder andere elegante Dinge. Die von mir hier vorgestellte Ansicht "Lazy-Listen" ist jedoch die häufigste Verwendung, die Sie finden werden.


Hinter den Kulissen

So funktioniert das "Python-Iterationsprotokoll". Das ist, was passiert, wenn Sie list(makeRange(5)) tun. Ich beschreibe das früher als "faule, inkrementelle Liste".

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Die integrierte Funktion next() ruft nur die Objekte .next() auf, die Teil des "Iterationsprotokolls" ist und auf allen Iteratoren vorhanden ist. Sie können die next()-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 würden sich die meisten Leute nicht für die folgenden Unterschiede interessieren und möchten hier wahrscheinlich aufhören zu lesen.

In Python-Sprache ist ein iterable ein Objekt, das "das Konzept einer for-Schleife" wie eine Liste [1,2,3] versteht, und ein Iterator ist eine bestimmte Instanz der angeforderten for-Schleife wie [1,2,3].__iter__(). Ein generator entspricht genau einem Iterator, mit Ausnahme der Schreibweise (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 lediglich eine Kopie von sich selbst.

In dem unwahrscheinlichen Fall, dass Sie so etwas nicht tun ...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... dann denken Sie daran, dass ein Generator ein Iterator ist; das heißt, es ist einmalig. Wenn Sie es wiederverwenden möchten, müssen Sie erneut myRange(...) aufrufen. Wenn Sie das Ergebnis zweimal verwenden müssen, konvertieren Sie das Ergebnis in eine Liste und speichern Sie es in einer Variablen x = list(myRange(5)). Diejenigen, die unbedingt einen Generator klonen müssen (z. B. furchtbar hackhafte Metaprogrammierung), können itertools.tee verwenden, wenn dies absolut notwendig ist, da der kopierbare Iterator Python PEP Standardvorschlag vorliegt aufgeschoben.

380
ninjagecko

Was macht das yield Schlüsselwort in Python?

Beantworten Sie Gliederung/Zusammenfassung

  • Eine Funktion mit yield liefert beim Aufruf einen Generator .
  • Generatoren sind Iteratoren, weil sie das Iteratorprotokoll implementieren, sodass Sie sie durchlaufen können.
  • Ein Generator kann auch gesendete Informationen sein, was ihn konzeptionell zu Koroutine macht.
  • In Python 3 können Sie delegieren mit yield from von einem Generator zu einem anderen in beide Richtungen.
  • (Anhang kritisiert einige Antworten, einschließlich der ersten, und erläutert die Verwendung von return in einem Generator.)

Generatoren:

yield ist nur innerhalb einer Funktionsdefinition zulässig, und Durch die Aufnahme von yield 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 wird die Ausführung des Codes zum Zeitpunkt der Ausgabe eingefroren . Wenn der Generator aufgerufen wird (Methoden werden unten erläutert), wird die Ausführung fortgesetzt und friert dann bei der nächsten Ausbeute ein.

yield bietet eine einfache Möglichkeit zum Implementieren des Iteratorprotokolls , das durch die folgenden beiden Methoden definiert wird: __iter__ und next (Python 2) oder __next__ (Python 3). Beide Methoden machen ein Objekt zu einem Iterator, den Sie mit der abstrakten Basisklasse Iterator aus dem Modul collections überprüfen können.

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

Der Generatortyp ist ein Subtyp des Iterators:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

Und wenn nötig, können wir das folgendermaßen überprüfen:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Ein Merkmal von Iteratorist, dass Sie es, sobald es erschöpft ist , nicht wiederverwenden oder zurücksetzen können:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

Sie müssen einen anderen erstellen, wenn Sie seine Funktionalität wieder verwenden möchten (siehe Fußnote 2):

>>> list(func())
['I am', 'a generator!']

Man kann Daten programmatisch ausgeben, zum Beispiel:

def func(an_iterable):
    for item in an_iterable:
        yield item

Der obige einfache Generator entspricht auch dem folgenden - ab Python 3.3 (und nicht verfügbar in Python 2)) können Sie yield from :

def func(an_iterable):
    yield from an_iterable

yield from Ermöglicht jedoch auch die Delegation an Untergeneratoren, was im folgenden Abschnitt zur kooperativen Delegation mit Unter-Koroutinen 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 Variable received, die auf die Daten verweist, die an den Generator gesendet werden:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

Zuerst müssen wir den Generator mit der eingebauten Funktion next in die Warteschlange stellen. Es wird die entsprechende next - oder __next__ - Methode aufgerufen, je nachdem, welche Version von Python Sie verwenden:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

Und jetzt können wir Daten in den Generator senden. ( Das Senden von None entspricht dem Aufrufen von next .):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Kooperative Delegation an Sub-Coroutine mit yield from

Denken Sie jetzt daran, dass yield from In Python= 3 verfügbar ist. Dadurch können wir Coroutinen an eine Subcoroutine delegieren:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

Und jetzt können wir die Funktionalität an einen Untergenerator delegieren und er kann von einem Generator wie oben verwendet werden:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

Weitere Informationen zur genauen Semantik von yield from Finden Sie in PEP 380.

Andere Methoden: schließen und werfen

Die Methode close löst GeneratorExit an dem Punkt aus, an dem die Funktionsausführung eingefroren wurde. Dies wird auch von __del__ Aufgerufen, so dass Sie jeden Bereinigungscode dort ablegen können, wo Sie mit dem GeneratorExit umgehen:

>>> my_account.close()

Sie können auch eine Ausnahme auslösen, die im Generator behandelt oder an den Benutzer weitergegeben werden kann:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Fazit

Ich glaube, ich habe alle Aspekte der folgenden Frage abgedeckt:

Was macht das yield Schlüsselwort in Python?

Es stellt sich heraus, dass yield viel tut. Ich bin mir sicher, dass ich noch ausführlichere Beispiele hinzufügen könnte. 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 **

  • Es ist verwirrt darüber, was ein iterabel ausmacht, nur am Beispiel einer Liste. Siehe meine Referenzen oben, aber zusammenfassend: Ein Iterable hat eine __iter__ - Methode, die einen Iterator zurückgibt. Ein Iterator liefert eine .next (Python 2 oder .__next__ (Python 3) -Methode, die implizit von for-Schleifen aufgerufen wird, bis StopIteration, und sobald dies der Fall ist, wird dies auch weiterhin durchgeführt.
  • Anschließend wird mithilfe eines Generatorausdrucks beschrieben, was ein Generator ist. Da ein Generator einfach ein bequemer Weg ist, einen Iterator zu erstellen, verwirrt er die Sache nur, und wir sind immer noch nicht zum Teil yield gelangt.
  • In Steuerung einer Generatorerschöpfung ruft er die Methode .next Auf, wenn er stattdessen die eingebaute Funktion next verwenden soll. Dies wäre eine angemessene Indirektionsebene, da sein Code in Python 3 nicht funktioniert.
  • Itertools? Dies war überhaupt nicht relevant für das, was yield tut.
  • Keine Diskussion der Methoden, die yield zusammen mit der neuen Funktionalität yield from In Python 3. Die oben/akzeptierte Antwort ist sehr unvollständig Antwort.

Antwortkritik, die yield in einem Generatorausdruck oder -verständnis vorschlägt.

Die Grammatik erlaubt derzeit jeden Ausdruck in einem Listenverständnis.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

Da Yield ein Ausdruck ist, wurde er von einigen als interessant angepriesen, um ihn in Begriffen oder als Generatorausdruck zu verwenden - obwohl kein besonders guter Anwendungsfall angegeben wurde.

Die CPython-Core-Entwickler diskutieren, wie sie ihre Zulage ablehnen . Hier ist ein relevanter Beitrag aus der Mailingliste:

Am 30. Januar 2017 um 19:05 schrieb Brett Cannon:

Am So, 29 Jan 2017 um 16:39 Craig Rodrigues schrieb:

Ich bin mit beiden Ansätzen einverstanden. Die Dinge so zu belassen, wie sie sind Python 3 ist meiner Meinung nach nicht gut.

Mein Votum ist, dass es sich um einen SyntaxError handelt, da Sie nicht das bekommen, was Sie von der Syntax erwarten.

Ich stimme zu, dass dies ein sinnvoller Ort für uns ist, da jeder Code, der sich auf das aktuelle Verhalten stützt, zu clever ist, um gewartet werden zu können.

In Bezug auf die Anreise werden wir wahrscheinlich wollen:

  • SyntaxWarning oder DeprecationWarning in 3.7
  • Py3k-Warnung in 2.7.x
  • SyntaxError in 3.8

Prost, Nick.

- Nick Coghlan | ncoghlan bei gmail.com | Brisbane, Australien

Außerdem gibt es ein ausstehendes Problem (10544) , das in die Richtung dieses niemals weist, was eine gute Idee ist (PyPy, a Python -Implementierung gibt bereits Syntaxwarnungen aus.)

Fazit, bis uns die Entwickler von CPython etwas anderes mitteilen: Setzen Sie yield nicht in einen Generatorausdruck oder ein Generatorverständnis.

Die Anweisung return in einem Generator

In Python 2 :

In einer Generatorfunktion darf die Anweisung return kein expression_list Enthalten. In diesem Zusammenhang zeigt ein bloßes return an, dass der Generator fertig ist und bewirkt, dass StopIteration ausgelöst wird.

Ein expression_list Ist im Grunde genommen eine beliebige Anzahl von Ausdrücken, die durch Kommas getrennt sind. Im Grunde genommen können Sie in Python 2 den Generator mit return anhalten, aber nicht Rückgabe eines Wertes.

In Python 3 :

In einer Generatorfunktion gibt die Anweisung return an, dass der Generator fertig ist, und bewirkt, dass StopIteration ausgelöst wird. Der zurückgegebene Wert (falls vorhanden) wird als Argument für die Erstellung von StopIteration verwendet und wird zum Attribut StopIteration.value.

Fußnoten

  1. Auf die Sprachen CLU, Sather und Icon wurde in dem Vorschlag verwiesen, das Konzept der Generatoren in Python einzuführen. Die allgemeine Idee ist, dass eine Funktion den internen Zustand aufrechterhalten und auf Anforderung des Benutzers Zwischendatenpunkte liefern kann. Dies versprach, in der Leistung anderen Ansätzen überlegen zu sein, einschließlich Python threading , das auf einigen Systemen nicht einmal verfügbar ist.

  2. Dies bedeutet zum Beispiel, dass xrange Objekte (range in Python 3) nicht Iterators sind, obwohl sie iterabel sind, Wie Listen geben ihre Methoden __iter__ Iteratorobjekte zurück.

  3. yield wurde ursprünglich als Anweisung eingeführt. Dies bedeutet, dass sie nur am Anfang einer Zeile in einem Codeblock stehen kann. Jetzt erzeugt yield einen Ertragsausdruck. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Diese Änderung wurde vorgeschlagen , damit ein Benutzer Daten so in den Generator senden kann, wie er sie möglicherweise empfängt. Um Daten senden zu können, muss man sie einem bestimmten Element zuordnen können. Daher funktioniert eine Anweisung einfach nicht.

331
Aaron Hall

yield ist wie return - es gibt das zurück, wozu Sie es sagen (als Generator). Der Unterschied ist, dass beim nächsten Aufruf des Generators die Ausführung vom letzten Aufruf an die yield-Anweisung beginnt. Im Gegensatz zu return wird der Stack-Frame nicht gesäubert, wenn eine Rendite auftritt, die Kontrolle wird jedoch an den Aufrufer zurückgegeben, so dass sein Zustand beim nächsten Aufruf der Funktion wieder aufgenommen wird.

Bei Ihrem Code verhält sich die Funktion get_child_candidates wie ein Iterator. Wenn Sie Ihre Liste erweitern, fügt sie der neuen Liste jeweils ein Element hinzu.

list.extend ruft einen Iterator auf, bis er erschöpft ist. Im Fall des von Ihnen geposteten Codebeispiels wäre es viel klarer, einfach ein Tuple zurückzugeben und dieses an die Liste anzufügen.

260
Douglas Mayle

Es gibt noch etwas zu erwähnen: Eine Funktion, die nachgibt, muss nicht wirklich enden. Ich habe folgenden Code geschrieben:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Dann kann ich es in anderem Code verwenden:

for f in fib():
    if some_condition: break
    coolfuncs(f);

Es hilft wirklich, einige Probleme zu vereinfachen und erleichtert die Arbeit mit einigen Dingen. 

199
Claudiu

TL; DR

An Stelle von:

def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

mach das:

def square_yield(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

Immer wenn Sie eine Liste von Grund auf neu erstellen, yield jedes Stück stattdessen.

Dies war mein erster "Aha" -Moment mit Ertrag.


yield ist eine zuckerhaltige Art zu sagen

baue eine Reihe von Sachen

Gleiches Verhalten:

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

Unterschiedliches Verhalten:

Die Ausbeute beträgt einmal : Sie können nur einmal durchlaufen. Wenn eine Funktion eine Ausbeute enthält, nennen wir sie Generatorfunktion . Und ein Iterator ist das, was es 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 einer Ausbeute wird beim Aufruf überhaupt nicht ausgeführt. Es wird ein Iterator-Objekt zurückgegeben, das sich daran erinnert, wo es aufgehört hat. Jedes Mal, wenn Sie next() auf dem Iterator aufrufen (dies geschieht in einer for-Schleife), wird die Ausführung um Zentimeter vorwärts zur nächsten Ausbeute ausgeführt. return löst StopIteration aus und beendet die Serie (dies ist das natürliche Ende einer for-Schleife).

Die Ausbeute ist vielseitig . Daten müssen nicht alle zusammen gespeichert werden, sondern können einzeln zur Verfügung gestellt werden. Es kann unendlich sein.

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

Wenn Sie mehrere Durchgänge benötigen und die Reihe nicht zu lang ist, rufen Sie einfach list() auf:

>>> list(square_yield(4))
[0, 1, 4, 9]

Geniale Wahl des Wortes yield weil beide Bedeutungen gilt:

Ertrag - produzieren oder liefern (wie in der Landwirtschaft)

... geben Sie die nächsten Daten der Serie an.

nachgeben - nachgeben oder aufgeben (wie in der politischen Macht)

... geben die CPU-Ausführung frei, bis der Iterator fortschreitet.

184
Bob Stein

Für diejenigen, die ein minimales Arbeitsbeispiel bevorzugen, meditieren Sie über diese interaktive Python Sitzung:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed
171
Daniel

Es gibt einen Generator zurück. Ich kenne Python nicht besonders gut, aber ich glaube, es ist dasselbe wie C # -Iteratorblöcke , wenn Sie mit diesen vertraut sind.

Die Schlüsselidee ist, dass der Compiler/Interpreter/was auch immer einige Tricks macht, so dass der Aufrufer weiter next () aufrufen kann und weiterhin Werte zurückgibt - als ob das Generatormethode wurde angehalten . Jetzt können Sie eine Methode offensichtlich nicht wirklich "pausieren", sodass der Compiler eine Zustandsmaschine erstellt, mit der Sie sich merken können, wo Sie sich gerade befinden und wie die lokalen Variablen usw. aussehen. Dies ist viel einfacher, als selbst einen Iterator zu schreiben.

161
Jon Skeet

Es gibt eine Art von Antwort, die meines Erachtens noch nicht gegeben wurde, unter den vielen tollen Antworten, die beschreiben, wie man Generatoren verwendet. Hier ist die Antwort auf die Theorie der Programmiersprache:

Die yield -Anweisung in Python gibt einen Generator zurück. Ein Generator in Python ist eine Funktion, die zurückgibt fortsetzungen (und insbesondere eine Art Koroutine, 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. Aber die Vorstellung, was eine Fortsetzung ist, ist einfach: Es ist der Zustand einer Berechnung, die 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 in diesen Zustand zurückgesetzt werden und die Operationen, die gespeichert wurden, ausgeführt werden.

Fortsetzungen in dieser allgemeineren Form können auf zwei Arten implementiert werden. Auf die call/cc Weise wird der Stapel des Programms buchstäblich gespeichert, und wenn die Fortsetzung aufgerufen wird, wird der Stapel wiederhergestellt.

Im Continuation-Passing-Stil (CPS) sind Fortsetzungen nur normale Funktionen (nur in Sprachen mit erstklassigen Funktionen), die der Programmierer explizit verwaltet und an Unterprogramme weitergibt. In diesem Stil wird der Programmstatus eher durch Closures (und die darin enthaltenen Variablen) dargestellt als durch Variablen, die sich irgendwo auf dem Stapel befinden. Funktionen, die den Kontrollfluss verwalten, akzeptieren Fortsetzungen als Argumente (in einigen Variationen von CPS akzeptieren Funktionen möglicherweise mehrere Fortsetzungen) und manipulieren den Kontrollfluss, indem sie sie aufrufen, indem sie sie einfach aufrufen und anschließend zurückkehren. Ein sehr einfaches Beispiel für den Weitergabestil lautet wie folgt:

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

In diesem (sehr simplen) Beispiel speichert der Programmierer die Operation des tatsächlichen Schreibens der Datei in eine Fortsetzung (die möglicherweise eine sehr komplexe Operation mit vielen Details zum Ausschreiben sein kann) und übergibt diese Fortsetzung dann (dh als Erstbefehl). Klassenabschluss) an einen anderen Operator weiter, der etwas mehr verarbeitet und ihn dann bei Bedarf aufruft. (Ich verwende dieses Entwurfsmuster häufig in der eigentlichen GUI-Programmierung, weil es mir Codezeilen erspart oder, was noch wichtiger ist, um den Steuerungsfluss nach dem Auslösen von GUI-Ereignissen zu verwalten.)

Der Rest dieses Beitrags wird ohne Verlust der Allgemeinheit Fortsetzungen als CPS konzipieren, weil es verdammt viel einfacher zu verstehen und zu lesen ist.


Lassen Sie uns nun über Generatoren in Python sprechen. Generatoren sind ein bestimmter Subtyp der Fortsetzung. Während Fortsetzungen im Allgemeinen den Status einer Berechnung (dh den Aufrufstapel des Programms) speichern können ), Generatoren können den Iterationsstatus nur über einen Iterator speichern. Diese Definition ist jedoch für bestimmte Anwendungsfälle von Generatoren leicht irreführend. Zum Beispiel:

def f():
  while True:
    yield 4

Dies ist eindeutig eine vernünftige Iteration, deren Verhalten genau definiert ist. Jedes Mal, wenn der Generator darüber iteriert, wird 4 zurückgegeben (und dies für immer). Aber es ist wahrscheinlich nicht der prototypische Typ von Iterable, der mir einfällt, wenn ich an Iteratoren denke (d. H. for x in collection: do_something(x)). Dieses Beispiel zeigt die Leistungsfähigkeit von Generatoren: Wenn es sich um einen Iterator handelt, kann ein Generator den Status seiner Iteration speichern.

Wiederholen: Durch Fortsetzungen kann der Status des Stacks eines Programms und durch Generatoren der Status der Iteration gespeichert werden. Dies bedeutet, dass Fortsetzungen viel leistungsfähiger sind als Generatoren, aber auch, dass Generatoren 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, sie zu lesen und zu verstehen diese Seite über Fortsetzungen und call/cc ).

Sie können Generatoren jedoch leicht als einfachen, spezifischen Fall eines Continuation-Passing-Stils implementieren (und konzeptualisieren):

Immer wenn yield aufgerufen wird, weist es die Funktion an, eine Fortsetzung zurückzugeben. Wenn die Funktion erneut aufgerufen wird, beginnt sie an der Stelle, an der sie aufgehört hat. Im Pseudopseudocode (d. H. Nicht im Pseudocode, aber nicht im Code) ist die next -Methode des Generators im Grunde wie folgt:

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

dabei ist das Schlüsselwort yield tatsächlich syntaktischer Zucker für die eigentliche Generatorfunktion.

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

Denken Sie daran, dass dies nur Pseudocode ist und die tatsächliche Implementierung von Generatoren in Python komplexer ist. Um zu verstehen, was gerade vor sich geht, sollten Sie den Continuation-Passing-Stil verwenden, um Generatorobjekte ohne das Schlüsselwort yield zu implementieren.

149
aestrivex

Die Ausbeute gibt Ihnen einen Generator. 

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

Wie Sie sehen, hält foo im ersten Fall die gesamte Liste auf einmal im Speicher. Es ist keine große Sache für eine Liste mit 5 Elementen, aber was ist, wenn Sie eine Liste von 5 Millionen wollen? Dies ist nicht nur ein großer Speicherfresser, es kostet auch viel Zeit, um zu bauen, wenn die Funktion aufgerufen wird. Im zweiten Fall erhalten Sie mit bar nur einen Generator. Ein Generator ist iterierbar - was bedeutet, dass Sie ihn in einer for-Schleife usw. verwenden können, aber auf jeden Wert nur einmal zugegriffen werden kann. Alle Werte werden auch nicht gleichzeitig gespeichert. Das Generatorobjekt "merkt sich", wo es sich beim letzten Aufruf in der Schleife befunden hat. Auf diese Weise müssen Sie, wenn Sie iterable verwenden, um bis zu 50 Milliarden zu zählen, nicht bis zu 50 Milliarden zählen sofort und speichern Sie die 50 Milliarden Zahlen, um durchzuzählen. Auch dies ist ein ziemlich durchdachtes 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 bereits gesagt haben, können Sie damit effiziente Permutationen schreiben, indem Sie mit Flows durch den Aufruf-Stack nach oben springen, anstatt eine Stack-Variable zu verwenden. Generatoren können auch für die spezialisierte Baumdurchquerung und für andere Zwecke verwendet werden.

148
RBansal

Hier ist ein Beispiel in Klartext. Ich werde eine Entsprechung zwischen menschlichen Konzepten auf hoher Ebene und Python-Konzepten auf niedriger Ebene bereitstellen.

Ich möchte mit einer Zahlenfolge arbeiten, aber ich möchte mich nicht mit der Erstellung dieser Folge beschäftigen. Ich möchte mich nur auf die Operation konzentrieren, die ich ausführen möchte. Also mache ich folgendes:

  • Ich rufe Sie an und sage Ihnen, dass ich eine Folge von Zahlen haben möchte, die auf eine bestimmte Weise produziert wird, und Sie wissen lassen, was der Algorithmus ist. 
    Dieser Schritt entspricht defim Generieren der Generatorfunktion, d. H. Der Funktion, die ein yield enthält.
  • Einige Zeit später sage ich Ihnen: "OK, machen Sie sich bereit, mir die Zahlenfolge mitzuteilen". 
    Dieser Schritt entspricht dem Aufruf der Generatorfunktion, die ein Generatorobjekt zurückgibt. Beachten Sie, dass Sie mir noch keine Zahlen nennen. Sie packen nur Papier und Bleistift.
  • Ich frage dich, "sag mir die nächste Nummer" und du sagst mir die erste Nummer; Danach warten Sie, bis ich Sie nach der nächsten Nummer frage. Es ist Ihre Aufgabe, sich daran zu erinnern, wo Sie waren, welche Zahlen Sie bereits gesagt haben und wie die nächste Zahl ist. Ich interessiere mich nicht für die Details. 
    Dieser Schritt entspricht dem Aufruf von .next() für das Generatorobjekt.
  • … Den vorherigen Schritt wiederholen, bis…
  • irgendwann wirst du vielleicht ein Ende haben. Du sagst mir keine Nummer; Sie schreien nur: "Halten Sie Ihre Pferde! Ich bin fertig! Keine weiteren Zahlen!" 
    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 beendet wird oder ein return ausgegeben wird.

Dies ist, was ein Generator tut (eine Funktion, die ein yield enthält); es wird ausgeführt, pausiert, wenn ein yield ausgeführt wird, und wenn nach einem .next()-Wert gefragt wird, wird es ab dem letzten Punkt fortgesetzt. Es passt perfekt zum Iterator-Protokoll von Python, das beschreibt, wie Werte nacheinander abgefragt werden.

Der bekannteste Benutzer des Iterator-Protokolls ist der Befehl for in Python. Also, wann immer Sie ein tun:

for item in sequence:

es spielt keine Rolle, ob sequence eine Liste, ein String, ein Wörterbuch oder ein Generator ist. object wie oben beschrieben; Das Ergebnis ist das gleiche: Sie lesen Elemente einer Sequenz nacheinander aus.

Beachten Sie, dass defin einer Funktion, die ein Schlüsselwort yield enthält, nicht die einzige Möglichkeit ist, einen Generator zu erstellen. Es ist einfach der einfachste Weg, einen zu erstellen.

Weitere Informationen finden Sie in der Python-Dokumentation über Iterator-Typen , die Yield-Anweisung und Generatoren .

120
tzot

Während viele Antworten zeigen, warum Sie einen yield verwenden, um einen Generator zu erstellen, gibt es mehr Verwendungen für yield. Es ist ziemlich einfach, eine Coroutine zu erstellen, die die Übertragung von Informationen zwischen zwei Codeblöcken ermöglicht. Ich werde keine der schönen Beispiele wiederholen, die bereits gegeben wurden, wie yield verwendet wurde, um einen Generator zu erstellen.

Um zu verstehen, was eine yield im folgenden Code bewirkt, können Sie mit dem Finger den Zyklus durch jeden Code verfolgen, der eine yield hat. Jedes Mal, wenn Ihr Finger auf die yield trifft, müssen Sie warten, bis eine next oder eine send eingegeben wird. Wenn eine next aufgerufen wird, durchlaufen Sie den Code, bis Sie auf yield tippen. Der Code rechts von yield wird ausgewertet und an den Anrufer zurückgegeben. Dann warten Sie. Wenn next erneut aufgerufen wird, führen Sie eine weitere Schleife durch den Code. Sie werden jedoch bemerken, dass yield in einer Coroutine auch mit einer send… verwendet werden kann, die einen Wert vom Aufrufer in die nachgebende Funktion sendet. Wenn eine send angegeben ist, empfängt yield den gesendeten Wert und spuckt ihn auf der linken Seite aus…. Dann wird der Trace durch den Code fortgesetzt, bis Sie erneut auf die yield tippen Wert am Ende, als ob next aufgerufen wurde).

Zum Beispiel:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
105
Mike McKerns

Es gibt noch eine andere yield Verwendung und Bedeutung (seit Python 3.3):

yield from <expr>

Aus PEP 380 - Syntax zum Delegieren an einen Subgenerator:

Es wird eine Syntax für einen Generator vorgeschlagen, um einen Teil seiner Operationen an einen anderen Generator zu delegieren. Dadurch kann ein Codeabschnitt, der "Yield" enthält, herausgerechnet und in einen anderen Generator eingefügt werden. Darüber hinaus darf der Untergenerator einen Wert zurückgeben, und der Wert wird dem delegierenden Generator zur Verfügung gestellt.

Die neue Syntax eröffnet auch einige Optimierungsmöglichkeiten, wenn ein Generator von einem anderen erzeugte Werte liefert.

Außerdem wird this (seit Python 3.5) eingeführt:

async def new_coroutine(data):
   ...
   await blocking_action()

um zu vermeiden, dass Coroutinen mit einem regulären Generator verwechselt werden (heute wird yield in beiden verwendet).

97

Alles gute Antworten, jedoch für Neulinge etwas schwierig.

Ich gehe davon aus, dass Sie die return-Anweisung gelernt haben.

Analog sind return und yield Zwillinge. return bedeutet "Rückkehr und Stopp", während "Rendite" "Rückkehr, aber weiter" bedeutet.

  1. Versuchen Sie, eine num_list mit return zu erhalten.
def num_list(n):
    for i in range(n):
        return i

Starte es:

In [5]: num_list(3)
Out[5]: 0

Sehen Sie, Sie erhalten nur eine einzige Nummer und keine Liste. return erlaubt niemals, dass Sie sich glücklich durchsetzen, nur einmal implementiert und beendet.

  1. Da kommt yield

Ersetzen Sie return durch yield:

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

Jetzt gewinnen Sie, um alle Zahlen zu erhalten.

Im Vergleich zu return, das einmal ausgeführt wird und stoppt, führt yield die von Ihnen geplanten Zeiten aus . Sie können return als return one of them und yield als return all of them interpretieren. Dies wird iterable genannt.

  1. Ein weiterer Schritt können wir yield-Anweisung mit return umschreiben.
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

Es ist der Kern von yield.

Der Unterschied zwischen einer Ausgabe der Liste return und der Ausgabe des Objekts yield ist:

Sie erhalten immer [0, 1, 2] von einem Listenobjekt, konnten sie jedoch nur einmal von 'dem Objekt yield-Ausgabe' abrufen. Es hat also einen neuen Namen generator, wie in Out[11]: <generator object num_list at 0x10327c990> angezeigt.

Abschließend als Metapher, um es zu grokieren:

  • return und yield sind Zwillinge
  • list und generator sind Zwillinge
86
JawSaw

Hier einige Python-Beispiele, wie Generatoren tatsächlich so implementiert werden können, als würde Python keinen syntaktischen Zucker für sie bereitstellen:

Als Python-Generator:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

Verwenden von lexikalischen Schließungen anstelle von Generatoren

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

Verwenden von Objektschließungen anstelle von Generatoren (weil ClosuresAndObjectsAreEquivalent )

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
83
Dustin Getz

Ich wollte "lesen Seite 19 von Beazleys" Python: Essential Reference "für eine schnelle Beschreibung von Generatoren", aber so viele andere haben bereits gute Beschreibungen veröffentlicht.

Beachten Sie auch, dass yield in Coroutines als das Dual ihrer Verwendung in Generatorfunktionen verwendet werden kann. Obwohl (yield) nicht dieselbe Verwendung wie Ihr Code-Snippet ist, kann es als Ausdruck in einer Funktion verwendet werden. Wenn ein Aufrufer einen Wert mit der Methode send() an die Methode sendet, wird die Coroutine ausgeführt, bis die nächste Anweisung (yield) gefunden wird.

Generatoren und Coroutinen sind eine coole Methode, um Datenflussanwendungen einzurichten. Ich dachte, es würde sich lohnen, die andere Verwendung der yield-Anweisung in Funktionen zu kennen.

81
johnzachary

Aus Programmiersicht 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, verwendet man Nachrichten, die an ein Abschlussobjekt gesendet werden, das über einen Dispatcher verfügt, und der Dispatcher antwortet auf "Messages".

http://en.wikipedia.org/wiki/Message_passing

"next" ist eine Nachricht, die an eine Schließung gesendet wurde, die vom Aufruf "iter" erstellt wurde.

Es gibt viele Möglichkeiten, diese Berechnung zu implementieren. Ich habe die Mutation verwendet, aber es ist leicht, dies ohne Mutation zu tun, indem der aktuelle Wert und der nächste Yielder 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.

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->
77
alinsoar

Hier ist ein einfaches Beispiel:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

Ausgabe:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

Ich bin kein Python-Entwickler, aber es scheint mir, dass yield die Position des Programmflusses hält und die nächste Schleife von der "Ertrag" -Position beginnt. Es scheint, als würde es an dieser Position warten, und kurz davor einen Wert nach außen zurückgeben, und das nächste Mal funktioniert es weiter.

Es scheint eine interessante und nette Fähigkeit zu sein: D

70
Engin OZTURK

Hier ist ein mentales Bild dessen, was yield tut.

Ich stelle mir vor, dass ein Thread einen Stack hat (auch wenn er nicht so implementiert ist).

Wenn eine normale Funktion aufgerufen wird, werden die lokalen Variablen in den Stack geschrieben, einige Berechnungen ausgeführt, der Stack wird gelöscht und die Funktion wird zurückgegeben. Die Werte seiner lokalen Variablen werden nie wieder angezeigt.

Wenn bei einer yield-Funktion der Code zu laufen beginnt (d. H. Nachdem die Funktion aufgerufen wurde und ein Generatorobjekt zurückgegeben wird, dessen next()-Methode dann aufgerufen wird), legt es seine lokalen Variablen auf ähnliche Weise auf dem Stack ab und berechnet eine Weile. Wenn er jedoch auf die Anweisung yield stößt, bevor er seinen Teil des Stapels löscht und zurückgibt, erstellt er eine Momentaufnahme seiner lokalen Variablen und speichert sie im Generatorobjekt. Es schreibt auch den Ort auf, an dem es aktuell in seinem Code ist (d. H. Die bestimmte yield-Anweisung).

Es ist also eine Art eingefrorene Funktion, an der der Generator hängt.

Wenn next() anschließend aufgerufen wird, ruft er die Eigenschaften der Funktion auf den Stack ab und animiert sie neu. Die Funktion berechnet weiter von dem Punkt aus, an dem sie aufgehört hatte, ohne zu wissen, dass sie gerade eine Ewigkeit in einem Kühlhaus verbracht hatte.

Vergleichen Sie die folgenden Beispiele:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

Wenn wir die zweite Funktion aufrufen, verhält sie sich ganz anders als die erste. Die yield-Anweisung ist möglicherweise nicht erreichbar, aber wenn sie irgendwo vorhanden ist, ändert sich die Art der Sache, mit der wir uns befassen.

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Beim Aufruf von yielderFunction() wird der Code nicht ausgeführt, sondern ein Generator aus dem Code erstellt. (Vielleicht ist es eine gute Idee, solche Dinge mit dem Präfix yielder zu benennen, um die Lesbarkeit zu erleichtern.)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

In den Feldern gi_code und gi_frame wird der eingefrorene Zustand gespeichert. Wenn wir sie mit dir(..) untersuchen, können wir bestätigen, dass unser mentales Modell oben glaubwürdig ist.

58
Evgeni Sergeev

Wie aus jeder Antwort hervorgeht, wird yield zum Erstellen eines Sequenzgenerators verwendet. Es wird zum dynamischen Erzeugen einer Sequenz verwendet. Während Sie zeilenweise eine Datei in einem Netzwerk lesen, können Sie die Funktion yield wie folgt verwenden:

def getNextLines():
   while con.isOpen():
       yield con.read()

Sie können es in Ihrem Code wie folgt verwenden:

for line in getNextLines():
    doSomeThing(line)

Ausführungskontrolltransfer gotcha

Die Ausführungssteuerung wird von getNextLines () an die for-Schleife übergeben, wenn die Ausbeute ausgeführt wird. Jedes Mal, wenn getNextLines () aufgerufen wird, beginnt die Ausführung an dem Punkt, an dem sie zuletzt angehalten wurde.

Also eine Funktion mit folgendem Code

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

wird drucken

"first time"
"second time"
"third time"
"Now some useful value 12"

(Meine unten stehende Antwort bezieht sich nur auf die Verwendung von Python generator, nicht auf die grundlegende Implementierung des Generatormechanismus , die einige Tricks der Stapel- und Heap-Manipulation beinhaltet.)

Wenn yield anstelle von return in einer python) - Funktion verwendet wird, wird diese Funktion in eine spezielle Funktion namens generator function Umgewandelt Ein Objekt vom Typ generator zurückgeben. Das Schlüsselwort yield ist ein Flag, mit dem der python Compiler benachrichtigt wird, wenn eine solche Funktion ausgeführt wird speziell. Normale Funktionen werden beendet, sobald ein bestimmter Wert von ihr zurückgegeben wird. Mit Hilfe des Compilers kann die Generatorfunktion überlegt werden als fortsetzbar, dh der Ausführungskontext wird wiederhergestellt und die Ausführung wird vom letzten Lauf fortgesetzt, bis Sie explizit return aufrufen, was eine StopIteration - Ausnahme auslöst (die auch Teil von ist Ich habe viele Referenzen zu generator gefunden, aber diese eine aus dem functional programming perspective ist die am besten verdauliche.

(Jetzt möchte ich über die Gründe für generator und iterator sprechen, die auf meinem eigenen Verständnis beruhen. Ich hoffe, dies kann Ihnen dabei helfen, die wesentliche Motivation von Iterator und Generator. Ein solches Konzept zeigt sich auch in anderen Sprachen wie C #.)

Soweit ich weiß, speichern wir Daten, wenn wir eine Reihe von Daten verarbeiten möchten, normalerweise zuerst irgendwo und verarbeiten sie dann nacheinander. Aber dieser naive Ansatz ist problematisch. Wenn das Datenvolumen sehr groß ist, ist es teuer, sie im Voraus als Ganzes zu speichern. Also, anstatt das data selbst direkt zu speichern, warum nicht indirekt eine Art metadata speichern, dh the logic how the data is computed.

Es gibt zwei Möglichkeiten, solche Metadaten zu verpacken.

  1. Der OO Ansatz, wir verpacken die Metadaten as a class. Dies ist der sogenannte iterator, der das Iterator-Protokoll implementiert (dh die __next__(), und __iter__() Methoden). Dies ist auch das häufig gesehene Iterator-Entwurfsmuster .
  2. Beim funktionalen Ansatz verpacken wir die Metadaten as a function. Dies ist der sogenannte generator function. Der zurückgegebene Iterator generator object Bleibt jedoch IS-A, Da er auch das Iterator-Protokoll implementiert.

In beiden Fällen wird ein Iterator erstellt, d. H. Ein Objekt, mit dem Sie die gewünschten Daten erhalten. Der OO Ansatz kann etwas komplex sein. Auf jeden Fall liegt es an Ihnen, welchen Sie verwenden.

46
smwikipedia

Ertrag ist ein Objekt

Eine return in einer Funktion gibt einen einzelnen Wert zurück.

Wenn Sie möchten, dass eine Funktion eine große Menge von Werten zurückgibt , verwenden Sie yield.

Noch wichtiger ist, yield ist eine Barriere .

wie die Barriere in der CUDA-Sprache überträgt sie die Kontrolle nicht, bis sie .__ erhält. abgeschlossen.

Das heißt, der Code in Ihrer Funktion wird von Anfang an ausgeführt, bis er auf yield trifft. Dann wird der erste Wert der Schleife zurückgegeben.

Bei jedem zweiten Aufruf wird dann die Schleife, die Sie in die Funktion geschrieben haben, noch einmal ausgeführt, und der nächste Wert wird zurückgegeben, bis kein Wert mehr vorhanden ist.

43
Kaleem Ullah

Zusammenfassend wandelt die yield-Anweisung Ihre Funktion in eine Factory um, die ein spezielles Objekt namens generator erzeugt, das sich um den Körper Ihrer ursprünglichen Funktion legt. Wenn die Variable generator iteriert wird, führt sie Ihre Funktion aus, bis sie die nächste Variable yield erreicht, unterbricht dann die Ausführung und wertet den Wert aus, der an yield übergeben wurde. Dieser Vorgang wird bei jeder Wiederholung wiederholt, bis der Ausführungspfad die Funktion beendet. Zum Beispiel,

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

einfach ausgänge

one
two
three

Die Leistung kommt von der Verwendung des Generators mit einer Schleife, die eine Sequenz berechnet. Der Generator führt die Schleife aus, indem er jedes Mal stoppt, um das nächste Ergebnis der Berechnung zu "erbringen". Auf diese Weise wird eine Liste "on the fly" berechnet für besonders große Berechnungen gespeichert

Angenommen, Sie wollten eine eigene range-Funktion erstellen, die einen iterierbaren Zahlenbereich erzeugt. Sie könnten es so machen.

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

und benutze es so;

for i in myRangeNaive(10):
    print i

Dies ist aber ineffizient, weil

  • Sie erstellen ein Array, das Sie nur einmal verwenden (dadurch wird Speicherplatz verschwendet).
  • Dieser Code durchläuft das Array tatsächlich zweimal! :(

Glücklicherweise waren Guido und sein Team großzügig genug, um Generatoren zu entwickeln, sodass wir dies einfach tun konnten.

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

Bei jeder Iteration führt nun eine Funktion im Generator mit dem Namen next() die Funktion aus, bis sie entweder eine 'yield'-Anweisung erreicht, in der sie stoppt und den Wert' nachgibt 'oder das Ende der Funktion erreicht. In diesem Fall führt next() beim ersten Aufruf bis zur Yield-Anweisung und Rendite 'n' aus, beim nächsten Aufruf führt es die Inkrement-Anweisung aus, springt zurück zum 'while', wertet es aus und wenn es wahr ist, stoppt es Wenn Sie erneut 'n' eingeben, wird der Vorgang fortgesetzt, bis die while-Bedingung falsch ist und der Generator zum Ende der Funktion springt.

42
redbandit

Viele Leute verwenden return anstelle von yield, aber in manchen Fällen kann yield effizienter und einfacher zu handhaben sein.

Hier ist ein Beispiel, für das yield definitiv das Beste ist:

return (in Funktion)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

yield (in Funktion)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

Aufrufende Funktionen

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

Beide Funktionen machen dasselbe, aber yield verwendet drei statt fünf Zeilen und hat eine Variable, um die man sich kümmern muss.

Dies ist das Ergebnis des Codes:

 Output

Wie Sie sehen, tun beide Funktionen dasselbe. Der einzige Unterschied ist, dass return_dates() eine Liste gibt und yield_dates() einen Generator liefert.

Ein reales Beispiel wäre zum Beispiel das Lesen einer Datei Zeile für Zeile oder wenn Sie nur einen Generator erstellen möchten.

40
Tom Fuller

yield ist wie ein Rückgabeelement für eine Funktion. Der Unterschied ist, dass das Element yield eine Funktion in einen Generator umwandelt. Ein Generator verhält sich wie eine Funktion, bis etwas "nachgegeben" wird. Der Generator hält an, bis er das nächste Mal aufgerufen wird, und fährt an genau dem Punkt fort, an dem er gestartet wurde. Sie können eine Folge aller "ergebenen" Werte in einem Wert erhalten, indem Sie list(generator()) aufrufen.

35
Theoremiser

Das yield-Schlüsselwort erfasst einfach die zurückgegebenen Ergebnisse. Denken Sie an yield wie return +=

35

Hier ist ein einfacher, auf yield basierender Ansatz zur Berechnung der Fibonacci-Reihe:

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

Wenn Sie dies in REPL eingeben und es dann versuchen und aufrufen, erhalten Sie ein rätselhaftes Ergebnis:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

Dies liegt daran, dass das Vorhandensein von yield Python signalisiert hat, dass Sie einen Generator erstellen möchten, dh ein Objekt, das bei Bedarf Werte generiert.

Wie generieren Sie diese Werte? Dies kann entweder direkt mit der integrierten Funktion next erfolgen oder indirekt, indem sie einem Konstrukt zugeführt wird, das Werte verbraucht. 

Mit der integrierten Funktion next() rufen Sie direkt .next/__next__ auf und zwingen den Generator, einen Wert zu erzeugen:

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

Indirekt, wenn Sie fib für eine for-Schleife, einen list-Initialisierer, einen Tuple-Initialisierer oder irgendetwas anderes bereitstellen, das ein Objekt erwartet, das Werte erzeugt/erzeugt, "verbrauchen" Sie den Generator, bis keine weiteren Werte mehr erzeugt werden können ( und es kehrt zurück):

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

Ähnlich mit einem Tuple-Initialisierer: 

>>> Tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

Ein Generator unterscheidet sich von einer Funktion dahingehend, dass er faul ist. Dies wird dadurch erreicht, dass der lokale Status beibehalten wird und Sie jederzeit wieder aufnehmen können. 

Wenn Sie fib zum ersten Mal aufrufen, indem Sie es aufrufen:

f = fib()

Python kompiliert die Funktion, stößt auf das Schlüsselwort yield und gibt einfach ein Generatorobjekt an Sie zurück. Nicht sehr hilfreich scheint es. 

Wenn Sie dann anfordern, dass der erste Wert direkt oder indirekt generiert wird, führt er alle gefundenen Anweisungen aus, bis er auf eine Variable yield stößt. Dann wird der Wert zurückgegeben, den Sie für yield angegeben haben, und wird angehalten. Für ein Beispiel, das dies besser veranschaulicht, verwenden wir einige print-Aufrufe (ersetzen Sie print "text", falls Python 2):

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

Geben Sie nun in der REPL ein:

>>> gen = yielder("Hello, yield!")

sie haben jetzt ein Generatorobjekt, das darauf wartet, dass ein Befehl einen Wert generiert. Verwenden Sie next und sehen Sie, was gedruckt wird:

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Die nicht zitierten Ergebnisse werden gedruckt. Das zitierte Ergebnis ist das, was von yield zurückgegeben wird. Rufen Sie jetzt erneut next auf:

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Der Generator merkt sich, dass er um yield value pausiert wurde, und fährt von dort fort. Die nächste Nachricht wird gedruckt und die Suche nach der yield-Anweisung, die angehalten werden soll, wird erneut ausgeführt (aufgrund der while-Schleife).

Ein einfaches Beispiel, was leicht zu erklären ist: yield

def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print i

Die Ausgabe ist: 

1 2 1 2 1 2 1 2
29
Gavriel Cohen

Noch eine andere TL; DR

Iterator in Liste: next() gibt das nächste Element der Liste zurück

Iterator-Generator: next() berechnet das nächste Element (Code ausführen)

Sie können den Yield/Generator als eine Möglichkeit sehen, den Kontrollfluss von außen manuell auszuführen (z. B. Schleife in einem Schritt weiterführen), indem Sie next aufrufen, auch wenn der Fluss komplex ist.

Hinweis: Der Generator ist NICHT eine normale Funktion. Es speichert den vorherigen Zustand wie lokale Variablen (Stack). Weitere Erklärungen finden Sie in anderen Antworten oder Artikeln. Der Generator kann nur einmal durchlaufen sein. Sie könnten ohne yield auskommen, aber es wäre nicht so schön, also kann es als "sehr schöner" Sprachzucker betrachtet werden.

28

Rendite ist ähnlich wie Rendite. Der Unterschied ist: 

-ertrag macht eine Funktion iterierbar (im folgenden Beispiel wird die Funktion primes(n = 1) iterierbar).
Was es im Wesentlichen bedeutet, ist, dass die Funktion beim nächsten Aufruf der Funktion dort fortgesetzt wird, wo sie verlassen wurde (was hinter der Zeile von yield expression liegt).

def isprime(n):
    if n == 1:
        return False
    for x in range(2, n):
        if n % x == 0:
            return False
    else:
        return True

def primes(n = 1):
   while(True):
       if isprime(n): yield n
       n += 1 

for n in primes():
    if n > 100: break
    print(n)

Wenn im obigen Beispiel isprime(n) wahr ist, wird die Primzahl zurückgegeben. In der nächsten Iteration wird ab der nächsten Zeile fortgefahren 

n += 1  
24
blueray

Alle Antworten hier sind großartig. aber nur einer von ihnen (der am meisten gewählte) bezieht sich auf wie Ihr Code funktioniert . Andere beziehen sich auf Generatoren im Allgemeinen und auf ihre Funktionsweise.

Also werde ich nicht wiederholen, was Generatoren sind oder was Erträge bewirken. Ich denke, dass diese durch bereits vorhandene Antworten abgedeckt werden. Nachdem ich jedoch einige Stunden damit verbracht habe, einen ähnlichen Code zu verstehen, baue ich die Funktionsweise auf.

Ihr Code durchläuft eine binäre Baumstruktur. Nehmen wir zum Beispiel diesen Baum:

    5
   / \
  3   6
 / \   \
1   4   8

Und noch eine einfachere Implementierung eines Binärsuchbaum-Durchlaufs:

class Node(object):
..
def __iter__(self):
    if self.has_left_child():
        for child in self.left:
            yield child

    yield self.val

    if self.has_right_child():
        for child in self.right:
            yield child

Der Ausführungscode befindet sich im Tree-Objekt, das __iter__ wie folgt implementiert:

def __iter__(self):

    class EmptyIter():
        def next(self):
            raise StopIteration

    if self.root:
        return self.root.__iter__()
    return EmptyIter()

Die while candidates-Anweisung kann durch for element in tree ersetzt werden. Python übersetzen dies in

it = iter(TreeObj)  # returns iter(self.root) which calls self.root.__iter__()
for element in it: 
    .. process element .. 

Da die Node.__iter__-Funktion ein Generator ist, wird der Code in ihm pro Iteration ausgeführt. Die Ausführung würde also so aussehen:

  1. wurzelelement ist zuerst; überprüfe, ob es childs verlassen hat und for iteriert (nenne es it1, weil es das erste Iterator-Objekt ist)
  2. es hat ein Kind, so dass for ausgeführt wird. Der for child in self.left erstellt einen neuen Iterator aus self.left, der selbst ein Knotenobjekt (it2) ist.
  3. Gleiche Logik wie 2, und ein neues iterator wird erstellt (it3)
  4. Nun erreichten wir das linke Ende des Baumes. it3 hat keine linken untergeordneten Elemente, so dass es fortgesetzt wird und yield self.value
  5. Beim nächsten Aufruf von next(it3) wird StopIteration ausgelöst und existiert, da es keine richtigen untergeordneten Elemente hat (es reicht bis zum Ende der Funktion, ohne dass irgendetwas nachgibt).
  6. it1 und it2 sind noch aktiv - sie sind nicht erschöpft und der Aufruf von next(it2) würde zu Werten führen und StopIteration nicht erhöhen.
  7. Jetzt sind wir wieder im it2-Kontext und rufen next(it2) auf, die dort weitermacht, wo sie aufgehört hat: direkt nach der yield child-Anweisung. Da es keine weiteren untergeordneten Elemente mehr gibt, wird es weitergegeben und es gibt seinen self.val.

Der Haken dabei ist, dass jede Iteration Unter-Iteratoren erstellt, um den Baum zu durchqueren und den Status des aktuellen Iterators enthält. Wenn das Ende erreicht ist, wird der Stapel zurückgefahren, und die Werte werden in der richtigen Reihenfolge zurückgegeben (kleinster Ergebniswert zuerst).

Ihr Codebeispiel hat in einer anderen Technik etwas Ähnliches getan: Es füllte eine One-Element-Liste für jedes Kind auf, dann wird es bei der nächsten Iteration aufgerufen und der Funktionscode für das aktuelle Objekt ausgeführt (daher self).

Ich hoffe, dass dies zu diesem legendären Thema etwas beigetragen hat. Ich habe mehrere Stunden damit verbracht, diesen Prozess zu zeichnen, um ihn zu verstehen.

10
Chen A.

Kurz gesagt, die Verwendung von yield ähnelt dem Schlüsselwort return, nur dass ein generator zurückgegeben wird.
Ein generator -Objekt wird nur für einmal durchlaufen.

yield hat zwei Vorteile: 

  1. Sie müssen diese Werte nicht zweimal lesen. 
  2. Sie können viele untergeordnete Knoten erhalten, ohne sie alle in den Speicher zu legen.
8
123

In Python werden generators (ein spezieller Typ von iterators) zum Generieren einer Reihe von Werten verwendet, und das yield-Schlüsselwort ist genau wie das return-Schlüsselwort der Generatorfunktionen. 

Das andere faszinierende yield-Schlüsselwort speichert die state einer Generatorfunktion

So können wir eine number bei jeder generator auf einen anderen Wert setzen. 

Hier ist ein Beispiel:

def getPrimes(number):
    while True:
        if isPrime(number):
            number = yield number     # a miracle occurs here
        number += 1

def printSuccessivePrimes(iterations, base=10):
primeGenerator = getPrimes(base)
primeGenerator.send(None)
for power in range(iterations):
    print(primeGenerator.send(base ** power))
7
ARGeo

Ausbeute

>>> def create_generator():
...    my_list = range(3)
...    for i in my_list:
...        yield i*i
...
>>> my_generator = create_generator() # create a generator
>>> print(my_generator) # my_generator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in my_generator:
...     print(i)
0
1
4

Kurz gesagt können Sie sehen, dass die Schleife nicht stoppt und weiterhin funktioniert, auch nachdem das Objekt oder die Variable gesendet wurde (im Gegensatz zu return, wo die Schleife nach der Ausführung stoppt).

6
Gavriel Cohen

Eine Analogie könnte helfen, die Idee hier zu erfassen:

Stellen Sie sich vor, Sie haben eine erstaunliche Maschine geschaffen, die Tausende und Abertausende von Glühbirnen pro Tag erzeugen kann. Die Maschine erzeugt diese Glühbirnen in Kartons mit einer eindeutigen Seriennummer. Sie haben nicht genug Platz, um alle diese Glühbirnen gleichzeitig zu lagern (d. H. Sie können die Geschwindigkeit der Maschine aufgrund von Lagerungsbeschränkungen nicht einhalten). Sie möchten diese Maschine daher anpassen, um Glühbirnen nach Bedarf zu erzeugen.

Python-Generatoren unterscheiden sich nicht wesentlich von diesem Konzept.

Stellen Sie sich vor, Sie haben eine Funktion x, die eindeutige Seriennummern für die Boxen generiert. Offensichtlich können Sie eine sehr große Anzahl solcher Barcodes durch die Funktion erzeugen lassen. Eine klügere und platzsparende Option besteht darin, diese Seriennummern bei Bedarf zu generieren.

Maschinencode:

def barcode_generator():
    serial_number = 10000  # Initial barcode
    while True:
        yield serial_number
        serial_number += 1


barcode = barcode_generator()
while True:
    number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
    barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
    print(barcodes)

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

    produce_more = input("Produce more? [Y/n]: ")
    if produce_more == "n":
        break

Wie Sie sehen können, haben wir eine eigenständige "Funktion", um jedes Mal die nächste eindeutige Seriennummer zu generieren. Diese Funktion gibt einen Generator zurück! Wie Sie sehen, rufen wir die Funktion nicht jedes Mal auf, wenn wir eine neue Seriennummer benötigen, sondern verwenden next(), wenn der Generator die nächste Seriennummer erhält.

Ausgabe:

How many lightbulbs to generate? 5
[10000, 10001, 10002, 10003, 10004]
Produce more? [Y/n]: y
How many lightbulbs to generate? 6
[10005, 10006, 10007, 10008, 10009, 10010]
Produce more? [Y/n]: y
How many lightbulbs to generate? 7
[10011, 10012, 10013, 10014, 10015, 10016, 10017]
Produce more? [Y/n]: n
6
Rafael

yield ist ein Typ von Generator, der in Python verwendet werden kann.

hier ist ein Link, um zu sehen, was Yield wirklich tut, auch in der Generierung . Generators & Yield Keyword - Python Central (PC)

Auch yield funktioniert wie return, aber auf eine andere Weise als return. Auch gibt es einen Link, der yield mehr erklärt, wenn Sie den anderen nicht so gut verstehen. Verbessere deine Ertragsfähigkeit - jeffknupp

In einfachen Worten ist „Ertrag“ ähnlich wie „Wert zurückgeben“, funktioniert jedoch mit Generator.

1
user3701435

In einfachen Ergebnissen gibt das Generatorobjekt anstelle von Werten zurück. 

Nachfolgend finden Sie ein einfaches Beispiel.

def sim_generator():
    for i in range(3):
        yield(i)

obj = sim_generator()
next(obj) # another way is obj.__next__()
next(obj)
next(obj)

der obige Code gibt 0, 1, 2 zurück

oder sogar kurz

for val in sim_generator():
    print(val)

return 0, 1, 2

Hoffe das hilft

1
Vivek Ananthan

Eine einfache Generatorfunktion

def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

die Fließanweisung hält die Funktion an, um alle ihre Zustände zu speichern, und setzt sie bei aufeinanderfolgenden Aufrufen später fort.

https://www.programiz.com/python-programming/generator

0

yield ergibt etwas. Es ist, als würde jemand Sie bitten, 5 Tassenkuchen zu machen. Wenn Sie mit mindestens einer Tasse Kuchen fertig sind, können Sie sie zum Essen geben, während Sie andere Kuchen backen.

In [4]: def make_cake(numbers):
   ...:     for i in range(numbers):
   ...:         yield 'Cake {}'.format(i)
   ...:

In [5]: factory = make_cake(5)

Hier wird factory als Generator bezeichnet, der Sie zum Kuchen macht. Wenn Sie make_function aufrufen, erhalten Sie einen Generator, anstatt diese Funktion auszuführen. Wenn yield in einer Funktion vorhanden ist, wird sie zum Generator.

In [7]: next(factory)
Out[7]: 'Cake 0'

In [8]: next(factory)
Out[8]: 'Cake 1'

In [9]: next(factory)
Out[9]: 'Cake 2'

In [10]: next(factory)
Out[10]: 'Cake 3'

In [11]: next(factory)
Out[11]: 'Cake 4'

Sie verbrauchten alle Kuchen, aber sie fragten wieder nach einem.

In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)

StopIteration:

und sie sollen aufhören, mehr zu fragen. Sobald Sie einen Generator verbraucht haben, sind Sie damit fertig. Sie müssen erneut make_cake aufrufen, wenn Sie mehr Kuchen wünschen. Es ist, als würde man eine weitere Bestellung für Cupcakes aufgeben.

In [13]: factory = make_cake(3)

In [14]: for cake in factory:
    ...:     print(cake)
    ...:
Cake 0
Cake 1
Cake 2

Sie können die for-Schleife auch mit einem Generator wie oben verwenden.

Noch ein Beispiel: Angenommen, Sie möchten ein zufälliges Passwort, wenn Sie danach fragen.

In [22]: import random

In [23]: import string

In [24]: def random_password_generator():
    ...:     while True:
    ...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
    ...:

In [25]: rpg = random_password_generator()

In [26]: for i in range(3):
    ...:     print(next(rpg))
    ...:
FXpUBhhH
DdUDHoHn
dvtebEqG

In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

Hier ist rpg ein Generator, der unendlich viele zufällige Passwörter generieren kann. Wir können also auch sagen, dass Generatoren nützlich sind, wenn wir die Länge der Sequenz nicht kennen, im Gegensatz zur Liste mit endlicher Anzahl von Elementen.

0
thavan