Frage

Was nützt das yield Schlüsselwort in Python?Was tut es?

Ich versuche zum Beispiel, 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 wird genannt?Wird eine Liste zurückgegeben?Ein einzelnes Element?Wird es noch einmal aufgerufen?Wann werden nachfolgende Anrufe eingestellt?


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

War es hilfreich?

Lösung

Was zu verstehen, was yield tut, du musst verstehen was Generatoren sind. Und bevor Sie Generatoren verstehen können, müssen Sie verstehen iterables.

Iterables

Wenn Sie eine Liste erstellen, können Sie ihre Elemente einzeln lesen. Das Lesen seiner Elemente nacheinander wird als Iteration bezeichnet:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist ist ein wiederholbar. Wenn Sie ein Listenverständnis verwenden, erstellen Sie eine Liste und somit iterable:

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

Alles was du benutzen kannst "for... in..."On ist iterable; lists, strings, Dateien ...

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

Generatoren

Generatoren sind Iteratoren, eine Art iterierbar Sie können nur einmal wiederholen. Generatoren speichern nicht alle Werte im Speicher. Sie erzeugen die Werte im laufenden Fliegen:

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

Es ist genauso, außer dass Sie benutzt haben () Anstatt von []. Aber du kann nicht ausführen for i in mygenerator Ein zweites Mal, da Generatoren nur einmal verwendet werden können: Sie berechnen 0, vergessen es dann und berechnen 1 und beenden 4, einzeln.

Ertrag

yield ist ein Schlüsselwort, das verwendet wird wie return, außer der Funktion gibt einen Generator zurück.

>>> 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 Reihe von Werten zurückgibt, die Sie nur einmal lesen müssen.

Meistern yield, Sie müssen das verstehen Wenn Sie die Funktion aufrufen, wird der Code, den Sie in der Funktionsbehörde geschrieben haben, nicht ausgeführt. Die Funktion gibt nur das Generatorobjekt zurück, das ist etwas schwierig :-)

Dann wird Ihr Code von dort fortgesetzt, wo er jedes Mal aufgehört hat for Verwendet den Generator.

Nun der schwierige Teil:

Das erste Mal das for Ruft das aus Ihrer Funktion erstellte Generatorobjekt auf. Er wird den Code in Ihrer Funktion von Anfang an ausgeführt, bis er trifft yield, Dann wird der erste Wert der Schleife zurückgegeben. Anschließend wird jeder Anruf die Schleife ausgeführt, die Sie noch einmal in der Funktion geschrieben haben, und gibt den nächsten Wert zurück, bis kein Wert zur Rückgabe besteht.

Der Generator wird als leer angesehen, sobald die Funktion ausgeführt wird, aber nicht schlägt yield mehr. Es kann daran liegen, dass die Schleife zu Ende gegangen war oder weil Sie eine nicht befriedigen "if/else" mehr.


Ihr Code erklärte

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 iteriert auf einer Liste, aber die Liste wird erweitert, während die Schleife iteriert wird :-) Es ist eine präzise Möglichkeit, all diese verschachtelten Daten zu durchlaufen, auch wenn sie etwas gefährlich ist, da Sie eine unendliche Schleife haben können. In diesem Fall, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) erschöpft alle Werte des Generators, aber while Erstellt immer wieder neue Generatorobjekte, die unterschiedliche Werte von den vorherigen erzeugen, da sie nicht auf demselben Knoten angewendet werden.

  • Das extend() Die Methode ist eine Listenobjektmethode, die eine iterable erwartet und der Liste ihre Werte hinzufügt.

Normalerweise übergeben wir eine Liste an sie:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

Aber in Ihrem Code erhält 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 in Erinnerung bleiben.

Und es funktioniert, weil Python egal ist, ob das Argument einer Methode eine Liste ist oder nicht. Python erwartet iterable, sodass es mit Saiten, Listen, Tupeln und Generatoren funktioniert! Dies nennt man Enten -Typing und ist einer der Grund, 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:

Kontrolle eines Generatorschö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
...

Notiz: Für Python 3 verwenden Sieprint(corner_street_atm.__next__()) oder print(next(corner_street_atm))

Es kann nützlich sein, um den Zugriff auf eine Ressource zu kontrollieren.

ITertools, dein bester Freund

Das iTertools -Modul enthält spezielle Funktionen zum Manipulieren von iterablen. Möchten Sie jemals einen Generator duplizieren? Generatoren der Kette zwei? Gruppenwerte in einer verschachtelten Liste mit einem One-Liner? Map / Zip Ohne eine andere Liste zu erstellen?

Dann einfach import itertools.

Ein Beispiel? Lassen Sie uns die möglichen Ankunftsbestellungen für ein Rennen mit vier Pferden 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)]

Verständnis der inneren Mechanismen der Iteration

Iteration ist ein Prozess, der iterables impliziert (implementiert die __iter__() Methode) und Iteratoren (Implementierung der __next__() Methode). Iterables sind alle Objekte, von denen Sie einen Iterator erhalten können. Iteratoren sind Objekte, mit denen Sie Iterables iterieren können.

In diesem Artikel gibt es mehr darüber wie for Schleifen funktionieren.

Andere Tipps

Abkürzung zum Verständnis yield

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

  1. Fügen Sie eine Zeile ein result = [] am Anfang der Funktion.
  2. Ersetzen Sie jedes yield expr mit result.append(expr).
  3. Fügen Sie eine Zeile ein return result am Ende der Funktion.
  4. Juhuu – nicht mehr yield Aussagen!Code lesen und herausfinden.
  5. Vergleichen Sie die Funktion mit der ursprünglichen Definition.

Dieser Trick gibt Ihnen vielleicht eine Vorstellung von der Logik hinter der Funktion, aber auch davon, was tatsächlich passiert yield unterscheidet sich erheblich von dem, was beim listenbasierten Ansatz geschieht.In vielen Fällen ist der Yield-Ansatz viel speichereffizienter und auch schneller.In anderen Fällen führt dieser Trick dazu, dass Sie 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

Zuerst die Iteratorprotokoll - wenn du schreibst

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

Python führt die folgenden zwei Schritte aus:

  1. Ruft einen Iterator für ab mylist:

    Anruf iter(mylist) -> Dies gibt ein Objekt mit a zurück next() Methode (bzw __next__() in Python 3).

    [Dies ist der Schritt, von dem die meisten Leute vergessen, Ihnen zu erzählen]

  2. Verwendet den Iterator, um Elemente zu durchlaufen:

    Rufen Sie weiter an next() Methode für den Iterator, der von Schritt 1 zurückgegeben wurde.Der Rückgabewert von next() zugeordnet ist x und der Schleifenkörper wird ausgeführt.Wenn eine Ausnahme StopIteration wird von innen heraus erhoben next(), bedeutet dies, dass im Iterator keine Werte mehr vorhanden sind und die Schleife beendet wird.

Die Wahrheit ist, dass Python die beiden oben genannten Schritte jederzeit ausführt Schleife über der Inhalt eines Objekts - es könnte also eine for-Schleife sein, es könnte aber auch codeartig sein otherlist.extend(mylist) (Wo otherlist ist eine Python-Liste).

Hier mylist ist ein wiederholbar weil es das Iteratorprotokoll implementiert.In einer benutzerdefinierten Klasse können Sie Folgendes implementieren __iter__() Methode, um Instanzen Ihrer Klasse iterierbar zu machen.Diese Methode sollte eine zurückgeben Iterator.Ein Iterator ist ein Objekt mit a next() Methode.Es ist möglich, beides umzusetzen __iter__() Und next() in der gleichen Klasse, und haben __iter__() zurückkehren self.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:

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

Beachten Sie, dass a for Die Schleife weiß nicht, mit welcher Art von Objekt sie es zu tun hat – sie folgt lediglich dem Iteratorprotokoll und freut sich, beim Aufrufen ein Element nach dem anderen abzurufen next().Integrierte Listen geben ihre Elemente einzeln zurück, Wörterbücher geben die zurück Schlüssel Eine nach der anderen geben die Dateien die zurück Linien eins nach dem anderen usw.Und die Generatoren kehren zurück ...Nun ja, dort ist es yield kommt herein:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Anstatt yield Aussagen, wenn Sie drei hätten return Aussagen in f123() nur der erste würde ausgeführt und die Funktion würde beendet.Aber f123() ist keine gewöhnliche Funktion.Wann f123() heißt es nicht Geben Sie einen der Werte in den Yield-Anweisungen zurück!Es gibt ein Generatorobjekt zurück.Außerdem wird die Funktion nicht wirklich beendet, sondern geht in einen angehaltenen Zustand über.Wenn das for Wenn die Schleife versucht, das Generatorobjekt zu durchlaufen, wird die Funktion aus ihrem angehaltenen Zustand in der nächsten Zeile nach dem fortgesetzt yield es zuvor zurückgekehrt ist, führt die nächste Codezeile aus, in diesem Fall a yield -Anweisung und gibt diese als nächstes Element zurück.Dies geschieht so lange, bis die Funktion beendet wird, woraufhin der Generator ansteigt StopIteration, und die Schleife wird beendet.

Das Generatorobjekt ist also so etwas wie ein Adapter – an einem Ende stellt es das Iteratorprotokoll bereit, indem es es offenlegt __iter__() Und next() Methoden, um die zu behalten for Schleife glücklich.Am anderen Ende führt es die Funktion jedoch gerade so weit aus, dass der nächste Wert aus ihr herausgeholt wird, und versetzt sie wieder in den angehaltenen Modus.

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 „Trick“ der temporären Liste zu verwenden.Das wird nicht in allen Fällen funktionieren, z.B.wenn Sie Endlosschleifen haben, oder es kann zu einer ineffizienten Speichernutzung kommen, wenn Sie eine wirklich lange Liste haben.Der andere Ansatz besteht darin, eine neue iterierbare Klasse zu implementieren SomethingIter Dadurch bleibt der Status in Instanzmitgliedern erhalten und der nächste logische Schritt wird darin ausgeführt next() (oder __next__() in Python 3) Methode.Abhängig von der Logik ist der Code innerhalb der next() Die Methode sieht am Ende möglicherweise sehr komplex aus und ist anfällig für Fehler.Hier bieten Generatoren eine saubere und einfache Lösung.

Denk darüber so:

Ein Iterator ist nur ein ausgefallener klingender Begriff für ein Objekt, das ein hat next() Methode. Eine Ertragsfunktion ist also so etwas wie folgt:

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

Für weitere Einblicke in das, was hinter den Kulissen passiert, die, die for Die Schleife kann dazu umgeschrieben werden:

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

Macht das mehr Sinn oder verwirren Sie einfach mehr? :)

Ich sollte beachten, dass dies ist eine zu vervollständige Vereinfachung für veranschaulichende Zwecke. :)

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

  1. Wenn der Compiler das erkennt yield Stichwort irgendwo In einer Funktion kehrt diese Funktion nicht mehr über die zurück return Aussage. Stattdessen, es sofort Gibt eine zurück faul "ausstehende Liste" Objekt Ein Generator genannt
  2. Ein Generator ist iterbar. Was ist ein wiederholbar? Es ist so etwas wie ein list oder set oder range oder dict-view mit a Eingebautes Protokoll zum Besuch jedes Elements in einer bestimmten Reihenfolge.

Kurzgesagt: Ein Generator ist eine faule, inkrementell ausführende Liste, und yield Mit Anweisungen können Sie Funktionsnotation verwenden, um die Listenwerte zu programmieren Der Generator sollte schrittweise ausspucken.

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

Lassen Sie uns eine Funktion definieren makeRange Das ist genau wie Python's range. Berufung makeRange(n) Gibt einen Generator zurück:

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 den Generator zu zwingen, seine anstehenden Werte sofort zurückzugeben, können Sie ihn übergeben list() (Genau wie Sie es jeder iterable könnten):

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

Vergleichen Sie das Beispiel mit "Nur eine Liste zurückgeben"

Das obige Beispiel kann als lediglich eine Liste erstellen, an die Sie anhängen und die Sie zurückgeben:

# 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 großen Unterschied; Siehe den letzten Abschnitt.


Wie Sie Generatoren verwenden könnten

Ein iterabler ist der letzte Teil eines Listenverständnisses, und alle Generatoren sind iterbar, sodass sie oft wie SO verwendet werden:

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

Um ein besseres Gefühl für Generatoren zu bekommen, können Sie mit dem herumspielen itertools Modul (achten Sie darauf chain.from_iterable statt chain wenn gerechtfertigt). Zum Beispiel können Sie sogar Generatoren verwenden, um unendlich lange faule Listen wie zu implementieren itertools.count(). Sie könnten Ihre eigenen implementieren def enumerate(iterable): zip(count(), iterable), oder alternativ mit dem yield Schlüsselwort in einer Weile-Loop.

Bitte beachten Sie: Generatoren können tatsächlich für viele weitere Dinge verwendet werden, wie z. Implementierung von Coroutinen oder nicht deterministische Programmierung oder andere elegante Dinge. Der Standpunkt "Lazy Lists", den ich hier präsentiere, ist jedoch die häufigste Verwendung, die Sie finden.


Hinter den Kulissen

So funktioniert das "Python -Iteration -Protokoll". Das heißt, was ist los, wenn Sie dies tun list(makeRange(5)). Dies beschreibe ich 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 einfach die Objekte auf .next() Funktion, die Teil des "Iterationsprotokolls" ist und auf allen Iteratoren zu finden ist. Sie können das manuell verwenden next() Funktion (und andere Teile des Iterationsprotokolls), um ausgefallene Dinge zu implementieren, normalerweise auf Kosten der Lesbarkeit. Versuchen Sie also, dies zu vermeiden ...


Minutien

Normalerweise kümmern sich die meisten Menschen nicht um die folgenden Unterschiede und möchten wahrscheinlich hier aufhören zu lesen.

In Python-Speak, an wiederholbar ist jedes Objekt, das "das Konzept einer For-Schleife versteht" wie eine Liste [1,2,3], und ein Iterator ist eine spezifische Instanz des angeforderten For-Loop-Like [1,2,3].__iter__(). EIN Generator ist genau das gleiche wie jeder Iterator, mit Ausnahme der Art und Weise, wie es geschrieben wurde (mit Funktionssyntax).

Wenn Sie einen Iterator aus einer Liste anfordern, werden ein neuer Iterator erstellt. Wenn Sie jedoch einen Iterator von einem Iterator anfordern (was Sie selten tun würden), gibt es Ihnen nur 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 ist Iterator; Das heißt, es ist einmalige Nutzung. Wenn Sie es wiederverwenden möchten, sollten Sie anrufen myRange(...) wieder. 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 (zum Beispiel, der schrecklich hackische Metaprogrammierung macht), kann verwenden itertools.tee wenn es unbedingt erforderlich ist, da der kopierbare Iterator Python PEP Der Vorschlag für Standards wurde verschoben.

Was macht das yield Schlüsselwort in Python?

Antwortübersicht/Zusammenfassung

  • Eine Funktion mit yield, wenn gerufen, Gibt eine zurück Generator.
  • Generatoren sind Iteratoren, weil sie die implementieren Iteratorprotokoll, Sie können sie über sie iterieren.
  • Ein Generator kann auch sein gesendete Informationen, konzeptionell a Coroutine.
  • In Python 3 können Sie delegieren von einem Generator zum anderen in beide Richtungen mit yield from.
  • (Anhang kritisiert einige Antworten, einschließlich der obersten, und erörtert die Verwendung von return in einem Generator.)

Generatoren:

yield ist nur in einer Funktionsdefinition legal und die Aufnahme von yield In einer Funktionsdefinition macht es einen Generator zurück.

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 gefroren am Punkt des Ertrags. Wenn der Generator aufgerufen wird (Methoden werden unten diskutiert), wird die Ausführung von Ausführungsaufnahmen und friert dann bei der nächsten Ertrag ein.

yield bietet eine einfache Möglichkeit zu Implementierung des Iteratorprotokolls, definiert durch die folgenden zwei Methoden:__iter__ und next (Python 2) oder __next__ (Python 3). Beide Methoden machen ein Objekt zu einem Iterator, den Sie mit dem tippen können Iterator Abstrakte Basisklasse aus der collections Modul.

>>> 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 Teil des Iterators:

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

Und falls erforderlich, können wir wie folgt typern:

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

Ein Merkmal eines Iterator ist das einmal erschöpft, Sie können es nicht wiederverwenden oder zurücksetzen:

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

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

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

Man kann Daten programmgesteuert liefern, z. B.:

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

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

def func(an_iterable):
    yield from an_iterable

Jedoch, yield from Ermöglicht auch die Delegation an Subgeneratoren, die im folgenden Abschnitt über die kooperative Delegation mit Subkoroutinen erläutert werden.

Coroutinen:

yield bildet einen Ausdruck, mit dem Daten an den Generator gesendet werden können (siehe Fußnote 3)

Hier ist ein Beispiel: Beachten Sie die received Variable, 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)

Zunächst müssen wir den Generator mit der gebauten Funktion anstellen, next. Es wird das angemessen nennen next oder __next__ Methode, abhängig von der Version von Python, die Sie verwenden:

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

Und jetzt können wir Daten in den Generator senden. (Senden None ist dasselbe wie anzurufen next.) :

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

Genossenschaftsdelegation zu Sub-Coroutine mit yield from

Erinnern Sie sich jetzt daran yield from ist in Python 3 erhältlich. Dadurch können wir Coroutinen an eine Subkoroutine 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 Subgenerator delegieren und von einem Generator genau 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

Sie können mehr über die genaue Semantik von lesen yield from in Pep 380.

Andere Methoden: Schließen und werfen

Das close Methode erhöht GeneratorExit An dem Punkt wurde die Funktionsausführung eingefroren. Dies wird auch von aufgerufen __del__ Sie können also jeden Reinigungscode einlegen, in dem Sie das verarbeiten GeneratorExit:

>>> my_account.close()

Sie können auch eine Ausnahme auslegen, die im Generator behandelt oder an den Benutzer zurückgegeben 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 behandelt:

Was macht das yield Schlüsselwort in Python?

Es stellt sich heraus, dass yield macht viel. Ich bin sicher, ich könnte dem noch gründlicheren Beispiele hinzufügen. Wenn Sie mehr wollen oder konstruktive Kritik haben, lassen Sie es mich wissen, indem Sie unten kommentieren.


Anhang:

Kritik der oberen/akzeptierten Antwort **

  • Es ist verwirrt darüber, was eine macht wiederholbar, Verwenden Sie einfach eine Liste als Beispiel. Siehe meine Referenzen oben, aber zusammenfassend: Ein iterable hat eine __iter__ Methode zurückgeben und Iterator. Ein Iterator bietet a .next (Python 2 oder .__next__ (Python 3) -Methode, die implizit von von genannt wird for Schleifen, bis es aufsteigt StopIteration, und sobald es es tut, wird es dies auch weiterhin tun.
  • Anschließend wird ein Generatorausdruck verwendet, um zu beschreiben, was ein Generator ist. Da ein Generator einfach eine bequeme Möglichkeit ist, eine zu erstellen Iterator, Es verwirrt nur die Angelegenheit, und wir sind immer noch nicht zur yield Teil.
  • Im Kontrolle eines Generatorschöpfung er ruft das .next Methode, wenn er stattdessen die integrierte Funktion verwenden sollte, next. Es wäre eine angemessene Indirektionschicht, da sein Code in Python 3 nicht funktioniert.
  • Itertools? Dies war nicht relevant für was yield macht überhaupt.
  • Keine Diskussion der Methoden, die yield Bietet zusammen mit der neuen Funktionalität yield from in Python 3. Die obere/akzeptierte Antwort ist eine sehr unvollständige Antwort.

Antwortkritik vorschlagen yield in einem Generatorausdruck oder -verständnis.

Die Grammatik ermöglicht 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 der Ertrag ein Ausdruck ist, wurde er von einigen als interessant angepriesen, um sie in Verständnissen oder Generatorausdruck zu verwenden - trotz des Zitierens eines besonders guten Anwendungsfalls.

Die CPython -Kernentwickler sind Diskussion über das Abwertung seiner Zulage. Hier ist ein relevanter Beitrag aus der Mailingliste:

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

Am Sun, 29. Januar 2017 um 16:39 Uhr Craig Rodrigues schrieb:

Ich bin mit beiden Ansätzen in Ordnung. Die Dinge so zu lassen, wie sie in Python 3 sind, ist nicht gut, IMHO.

Meine Stimme ist, dass es eine SyntaxEerror ist, da Sie nicht das bekommen, was Sie von der Syntax erwarten.

Ich würde zustimmen, dass dies ein vernünftiger Ort für uns ist, da jeder Code, der sich auf das aktuelle Verhalten stützt, wirklich zu klug ist, um aufrechterhalten zu werden.

Wenn wir dorthin gelangen, wollen wir wahrscheinlich:

  • SyntaxWarning oder Abschaltspiegelung 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 eine Hervorragende Ausgabe (10544) das scheint in Richtung dieses noch nie Eine gute Idee (Pypy, eine in Python geschriebene Python -Implementierung, erhöht bereits Syntaxwarnungen.)

Fazit, bis die Entwickler von CPython uns etwas anderes mitteilen: Nicht ausdrücken yield in einem Generatorausdruck oder -verständnis.

Das return Erklärung in einem Generator

Im Python 2:

In einer Generatorfunktion die return Aussage darf keine enthalten expression_list. In diesem Zusammenhang ein kahl return zeigt an, dass der Generator fertig ist und verursacht StopIteration aufgezogen werden.

Ein expression_list ist im Grunde eine beliebige Anzahl von Ausdrücken, die durch Kommas getrennt sind - im Wesentlichen in Python 2 können Sie den Generator mit stoppen return, aber Sie können keinen Wert zurückgeben.

Im Python 3:

In einer Generatorfunktion die return Die Anweisung zeigt an, dass der Generator fertig ist und verursacht wird StopIteration aufgezogen werden. Der zurückgegebene Wert (falls vorhanden) wird als Argument zum Konstruktion verwendet StopIteration und wird das StopIteration.value Attribut.

Fußnoten

  1. Die Sprachen, CLU, SATHER und ICON, wurden im Vorschlag zur Einführung des Konzepts der Generatoren in Python verwiesen. Die allgemeine Idee ist, dass eine Funktion den internen Zustand aufrechterhalten und durch den Benutzer mittlere Datenpunkte bei Bedarf liefern kann. Dies versprach zu sein Überlegen in der Leistung gegenüber anderen Ansätzen, einschließlich Python -Threading, was in einigen Systemen nicht einmal verfügbar ist.

  2. Dies bedeutet zum Beispiel das xrange Objekte (range in Python 3) sind nicht IteratorS, obwohl sie iterbar sind, weil sie wiederverwendet werden können. Wie Listen, ihre __iter__ Methoden geben Iteratorobjekte zurück.

  3. yield wurde ursprünglich als Aussage eingeführt, was bedeutet, dass es nur am Anfang einer Zeile in einem Codeblock erscheinen konnte. Jetzt yield erstellt einen Ertragsausdruck.https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Diese Änderung war vorgeschlagen Damit ein Benutzer Daten in den Generator senden kann, wie es möglicherweise empfangen wird. Um Daten zu senden, muss man sie etwas zuweisen können, und dafür wird eine Erklärung einfach nicht funktionieren.

yield ist genau wie return - Es gibt alles zurück, was Sie ihm sagen (als Generator). Der Unterschied besteht darin, dass die Ausführung beim nächsten Mal, wenn Sie den Generator anrufen yield Aussage. Im Gegensatz zur Rückkehr, Der Stapelrahmen wird nicht aufgeräumt, wenn eine Ausbeute auftritt. Die Kontrolle wird jedoch wieder an den Anrufer übertragen, sodass sein Zustand beim nächsten Aufrufen der Funktion wieder aufgenommen wird.

Bei Ihrem Code die Funktion get_child_candidates Benimmt sich wie ein Iterator, so dass beim Erweitern Ihrer Liste die neue Liste jeweils ein Element hinzugefügt wird.

list.extend Ruft einen Iterator an, bis er erschöpft ist. Bei dem von Ihnen veröffentlichten Code -Beispiel wäre es viel klarer, nur ein Tupel zurückzugeben und dies an die Liste anzuhängen.

Es gibt eine zusätzliche Sache zu erwähnen: Eine Funktion, die die Ergabungen nicht enden muss. Ich habe einen solchen Code geschrieben:

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

Dann kann ich es in einem anderen Code wie diesem verwenden:

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

Es hilft wirklich, einige Probleme zu vereinfachen und erleichtert einige Dinge, mit denen es leichter zu arbeiten ist.

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

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.

Wann immer Sie sich von Grund auf neu erstellen, erstellen Sie eine Liste yield Jedes Stück stattdessen.

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


yield ist ein Zucker Weg zu sagen

Bauen Sie eine Reihe von Sachen auf

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

Anderes Verhalten:

Ertrag ist Einzelpass: Sie können nur einmal durcharbeiten. Wenn eine Funktion eine Ausbeute hat, nennen wir sie a Generatorfunktion. Und ein Iterator ist das, was es zurückkehrt. Diese Begriffe enthüllen. Wir verlieren die Bequemlichkeit eines Containers, erhalten jedoch die Kraft einer Serie, die nach Bedarf und willkürlich lang berechnet wird.

Ertrag ist faul, Es wird die Berechnung abgeleitet. Eine Funktion mit einer Ausbeute darin Fügt sich eigentlich überhaupt nicht aus, wenn Sie es anrufen. Es gibt an ein Iteratorobjekt Das erinnert sich, wo es aufgehört hat. Jedes Mal, wenn Sie anrufen next() Auf dem Iterator (dies geschieht in einer For-Loop-Ausführung) ist ein Zentimeter des nächsten Ertrags. return Erhöht die Stopperation und beendet die Serie (dies ist das natürliche Ende einer For-Schleife).

Ertrag ist vielseitig. Daten müssen nicht alle zusammen gespeichert werden, sondern kann nacheinander verfügbar gemacht 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 brauchen mehrere Pässe Und die Serie ist nicht zu lang, rufen Sie einfach an list() darauf:

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

Brillante Wahl des Wortes yield Weil Beide Bedeutungen anwenden:

Ertrag - produzieren oder bereitstellen (wie in der Landwirtschaft)

... Geben Sie die nächsten Daten in der Serie an.

Ertrag - Geben oder aufgeben (wie in politischer Macht)

... Die CPU -Ausführung aufgeben, bis der Iterator voranschreitet.

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 können, im ersten Fall foo Hält die gesamte Liste gleichzeitig 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 wünschen? Dies ist nicht nur ein riesiger Speicherfresser, sondern auch viel Zeit, um zu dem Zeitpunkt zu bauen, in dem die Funktion aufgerufen wird.

Im zweiten Fall, bar Gibt Ihnen nur einen Generator. Ein Generator ist iterbar-was bedeutet, dass Sie ihn in a verwenden können for Schleife usw., aber auf jeden Wert kann nur einmal zugegriffen werden. Alle Werte werden auch nicht gleichzeitig im Speicher gespeichert. Das Generatorobjekt "erinnert", wo es sich in der Looping befand sofort und lagern Sie die 50 Milliarden Zahlen, die Sie durchlaufen müssen.

Auch dies ist ein ziemlich erfundenes Beispiel. Sie würden wahrscheinlich Itertools verwenden, wenn Sie wirklich 50 Milliarden zählen wollten. :)

Dies ist der einfachste Anwendungsfall von Generatoren. Wie Sie bereits sagten, kann es verwendet werden, um effiziente Permutationen zu schreiben, wobei die Rendite die Dinge durch den Anrufstapel nach oben drückt, anstatt eine Art Stapelvariable zu verwenden. Generatoren können auch für spezialisierte Baumquellen und alle möglichen anderen Dinge verwendet werden.

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

Die Hauptidee ist, dass der Compiler/Interpreter/was auch immer einige Tricks macht, damit der Anrufer weiterhin als nächstes anrufen kann () und es wird weiterhin Werte zurückgeben - Als ob die Generatormethode durchgeführt wurde. Jetzt können Sie offensichtlich keine Methode "innehalten", sodass der Compiler eine staatliche Maschine erstellt, mit der Sie sich daran erinnern können, wo Sie derzeit sind und wie die lokalen Variablen usw. aussehen. Dies ist viel einfacher, als selbst einen Iterator zu schreiben.

Es gibt eine Art von Antwort, die ich noch nicht für gegeben habe, unter den vielen großartigen Antworten, die beschreiben, wie man Generatoren benutzt. Hier ist die Programmiersprache Theorie Antwort:

Das yield Die Erklärung in Python gibt einen Generator zurück. Ein Generator in Python ist eine Funktion, die zurückgibt Kontinuationen (Und insbesondere eine Art von Korutine, aber Kontinuationen repräsentieren den allgemeineren Mechanismus, um zu verstehen, was vor sich geht).

Kontinuationen in der Theorie der Programmiersprachen sind eine viel grundlegendere Art der Berechnung, aber sie werden nicht oft verwendet, da sie äußerst schwer zu argumentieren und auch sehr schwierig zu implementieren sind. Aber die Idee, was eine Fortsetzung ist, ist unkompliziert: Es ist der Zustand einer Berechnung, die noch nicht beendet ist. In diesem Zustand werden die aktuellen Werte von Variablen, die Operationen, die noch nicht durchgeführt werden, und so weiter, gespeichert. Irgendwann später im Programm kann die Fortsetzung aufgerufen werden, so dass die Variablen des Programms auf diesen Zustand zurückgesetzt werden und die gespeicherten Operationen durchgeführt werden.

Kontinuationen können in dieser allgemeineren Form auf zwei Arten implementiert werden. In dem call/cc Wie der Stapel des Programms buchstäblich gespeichert ist und dann, wenn die Fortsetzung aufgerufen wird, wird der Stapel wiederhergestellt.

Im Continuation Passing Style (CPS) sind Kontinuationen nur normale Funktionen (nur in Sprachen, in denen Funktionen erstklassig sind), die der Programmierer ausdrücklich verwaltet und zu Unterprogrammen übergeht. In diesem Stil wird der Programmstatus durch Schließungen (und die Variablen, die zufällig codiert werden) und nicht durch Variablen dargestellt, die sich irgendwo auf dem Stapel befinden. Funktionen, die den Kontrollfluss verwalten, akzeptieren Fortsetzung als Argumente (in einigen Variationen von CPS können Funktionen mehrere Kontinuationen akzeptieren) und den Kontrollfluss manipulieren, indem sie sie aufrufen, indem sie einfach angerufen und anschließend zurückkehren. Ein sehr einfaches Beispiel für den Fortsetzung des Passingstils ist 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 den Betrieb, die Datei tatsächlich in eine Fortsetzung zu schreiben (was möglicherweise eine sehr komplexe Operation mit vielen Details zum Aufschreiben sein kann) und übergibt diese Fortsetzung (dh als erste Klassenverschluss) an einen anderen Bediener, der eine weitere Verarbeitung durchführt, und nennt es dann bei Bedarf. (Ich verwende dieses Designmuster viel in der tatsächlichen GUI -Programmierung, entweder weil es mir Codezeilen speichert oder vor allem, um den Steuerfluss nach GUI -Ereignisauslöser zu verwalten.)

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


Lassen Sie uns nun über Generatoren in Python sprechen. Generatoren sind ein spezifischer Subtyp der Fortsetzung. Wohingegen Kontinuationen sind im Allgemeinen in der Lage, den Zustand von a zu retten Berechnung (dh der Anrufstack des Programms), Generatoren können nur den Zustand der Iteration vor einem retten Iterator. 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 ein vernünftiges iterbares, dessen Verhalten gut definiert ist - jedes Mal, wenn der Generator darüber iteriert, gibt es 4 zurück (und tut dies für immer). Aber es ist wahrscheinlich nicht die prototypische Art von Iterable, die mir beim Denken an Iteratoren (dh,, for x in collection: do_something(x)). Dieses Beispiel zeigt die Kraft der Generatoren: Wenn irgendetwas ein Iterator ist, kann ein Generator den Zustand seiner Iteration retten.

Zu wiederholen: Fortsetzung kann den Zustand des Stapels eines Programms retten, und Generatoren können den Zustand der Iteration retten. Dies bedeutet, dass Kontinuationen 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 leichter zu verwenden (wenn Sie etwas Zeit zum Brennen haben, versuchen Sie zu lesen und zu verstehen Diese Seite über Kontinuationen und Anruf/CC).

Sie könnten jedoch leicht Generatoren als einfachen, spezifischen Fall des Fortsetzung des Passing -Stils implementieren (und konzipieren):

Wann immer yield Es wird aufgerufen, die Funktion soll eine Fortsetzung zurückgeben. Wenn die Funktion erneut aufgerufen wird, beginnt sie von wo immer sie aufgehört hat. Also in pseudo-pseudocode (dh nicht pseudocode, aber nicht codieren) der Generator des Generators next Die Methode ist 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

bei dem die yield Schlüsselwort ist tatsächlich syntaktischer Zucker für die reale Generatorfunktion, im Grunde so etwas wie:

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. Versuchen Sie jedoch als Übung, um zu verstehen, was vor sich geht yield Stichwort.

Hier ist ein Beispiel in einfacher Sprache. Ich werde eine Korrespondenz zwischen hochrangigen menschlichen Konzepten für Python-Konzepte auf niedrigem Niveau bieten.

Ich möchte auf eine Folge von Zahlen arbeiten, aber ich möchte mich nicht mit der Erstellung dieser Sequenz stören, 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 Abfolge von Zahlen möchte, die auf eine bestimmte Weise erzeugt werden, und ich lasse Sie wissen, was der Algorithmus ist.
    Dieser Schritt entspricht defIn der Generatorfunktion, dh die Funktion mit a enthält a yield.
  • Irgendwann später sage ich Ihnen: "OK, mach dich bereit, mir die Abfolge der Zahlen zu erzählen."
    Dieser Schritt entspricht dem Aufrufen der Generatorfunktion, die ein Generatorobjekt zurückgibt. Beachten Sie, dass Sie mir noch keine Zahlen erzählen. Sie schnappen sich einfach Ihr Papier und Ihren Bleistift.
  • Ich frage Sie: "Sag mir die nächste Nummer", und du sagst mir die erste Nummer. Danach warten Sie darauf, dass 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. Die Details interessieren mich nicht.
    Dieser Schritt entspricht dem Anruf .next() auf dem Generatorobjekt.
  • … Wiederholen Sie den vorherigen Schritt, bis…
  • Schließlich könnten Sie ein Ende haben. Sie sagen mir keine Nummer; Du schreist nur: "Halt deine Pferde! Ich bin fertig! Keine Zahlen mehr!"
    Dieser Schritt entspricht dem Generatorobjekt, der seinen Job beendet und a erhöht StopIteration Ausnahme Die Generatorfunktion muss die Ausnahme nicht erhöhen. Es wird automatisch angehoben, wenn die Funktion endet oder a return.

Dies macht ein Generator (eine Funktion, die a enthält yield); Es beginnt auszuführen, pausiert, wenn es a tut yield, und wenn nach einem gefragt .next() Der Wert, den es von dem Punkt fortsetzt, war es zuletzt. Es passt perfekt zum Design mit dem Iteratorprotokoll von Python, das beschreibt, wie die Werte nacheinander anfordern.

Der berühmteste Benutzer des Iteratorprotokolls ist das for Befehl in Python. Also, wann immer Sie a:

for item in sequence:

Es spielt keine Rolle, ob sequence ist eine Liste, eine Zeichenfolge, ein Wörterbuch oder ein Generator Objekt wie oben beschrieben; Das Ergebnis ist das gleiche: Sie lesen Elemente von einer Sequenz nacheinander.

Beachten Sie, dass defin einer Funktion in einem a enthält a yield Schlüsselwort ist nicht der einzige Weg, um einen Generator zu erstellen. Es ist einfach der einfachste Weg, einen zu kreieren.

Für genauere Informationen lesen Sie über Iteratortypen, das Ertragserklärung und Generatoren In der Python -Dokumentation.

Während viele Antworten zeigen, warum Sie eine verwenden würden yield Um einen Generator zu erstellen, gibt es mehr Verwendungszwecke für yield. Es ist ziemlich einfach, eine Coroutine zu erstellen, die die Übergabe von Informationen zwischen zwei Codeblöcken ermöglicht. Ich werde keine der guten Beispiele wiederholen, die bereits über die Verwendung gegeben wurden yield So erstellen Sie einen Generator.

Um zu verstehen, was a yield In dem folgenden Code können Sie Ihren Finger verwenden, um den Zyklus durch jeden Code mit einem zu verfolgen yield. Jedes Mal, wenn Ihr Finger trifft, die yield, du musst auf eine warten next oder ein send eingetragen werden. Wenn ein next Es wird angerufen, Sie verfolgen den Code, bis Sie auf die getroffen haben yield… Der Code rechts von dem yield wird bewertet und an den Anrufer zurückgegeben ... dann warten Sie. Wann next Nochmals aufgerufen, führen Sie eine weitere Schleife durch den Code durch. Sie werden jedoch in einer Coroutine feststellen, yield kann auch mit a verwendet werden send… Das sendet einen Wert vom Anrufer hinein die Ausbeutefunktion. Wenn ein send wird gegeben, dann yield Empfängt den gesendeten Wert und spuckt ihn auf der linken Seite aus ... dann geht die Spur durch den Code fort yield Wieder (zurückgeben des Wertes am Ende, als ob next hieß).

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()

Es gibt einen anderen yield Verwendung und Bedeutung (seit Python 3.3):

yield from <expr>

Aus PEP 380 - Syntax zum Delegieren an einen Subgenerator:

Eine Syntax wird vorgeschlagen, damit ein Generator einen Teil seiner Operationen an einen anderen Generator delegieren kann. Auf diese Weise kann ein Codeabschnitt, der "Ertrag" enthält, berücksichtigt und in einen anderen Generator platziert werden. Darüber hinaus darf der Subgenerator mit einem Wert zurückgeben, und der Wert wird dem Delegierergenerator zur Verfügung gestellt.

Die neue Syntax eröffnet auch einige Möglichkeiten zur Optimierung, wenn ein Generator, das von einem anderen erstellt wird, neu erstellt.

Darüber hinaus Dies wird einführen (seit Python 3.5):

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

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

Alle tollen Antworten, jedoch ein bisschen schwierig für Neulinge.

Ich nehme an, Sie haben das gelernt return Aussage.

Als Analogie, return und yield sind Zwillinge. return bedeutet "Rückkehr und Stopp", während "Rendite" zurückkehrt, aber weitermachen "

  1. Versuchen Sie, eine num_list mit zu erhalten return.
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 Zahl und nicht eine Liste von ihnen. return Lassen Sie sich niemals glücklich durchsetzen, implementiert nur einmal und kündigt auf.

  1. Da kommt yield

Ersetzen return mit 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.

Verglichen mit return das läuft einmal und stoppt, yield Läuft Male, die Sie geplant haben. Sie können interpretieren return wie return one of them, und yield wie return all of them. Das nennt man iterable.

  1. Ein weiterer Schritt können wir neu schreiben yield Aussage mit return
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 um yield.

Der Unterschied zwischen einer Liste return Ausgänge und das Objekt yield Ausgabe ist:

Sie erhalten immer [0, 1, 2] aus einem Listenobjekt, können sie jedoch nur vom Objekt abrufen yield einmal ausgeben. Es hat also einen neuen Namen generator Objekt wie angezeigt in Out[11]: <generator object num_list at 0x10327c990>.

Abschließend als Metapher, um es zu grokieren:

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

Hier finden Sie einige Python -Beispiele, wie sie Generatoren tatsächlich implementieren können, als hätte Python keinen syntaktischen Zucker für sie bereitgestellt:

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 Verschlüssen 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 CloseuresandObjectsareequivalent)

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)

Ich wollte "Seite 19 von Beazleys 'Python: Essential Reference' für eine kurze Beschreibung der Generatoren lesen, aber so viele andere haben bereits gute Beschreibungen gepostet.

Beachten Sie auch, dass yield Kann in Coroutinen als Dual ihrer Verwendung in Generatorfunktionen verwendet werden. Obwohl es nicht die gleiche Verwendung ist wie Ihr Code -Snippet, (yield) kann als Ausdruck in einer Funktion verwendet werden. Wenn ein Anrufer mit der Methode einen Wert an die Methode sendet send() Methode, dann wird die Coroutine bis zum nächsten ausgeführt (yield) Aussage wird angetroffen.

Generatoren und Coroutinen sind eine coole Möglichkeit, Datenflussanwendungen einzurichten. Ich dachte, es würde sich lohnen, über den anderen Gebrauch der zu wissen yield Aussage in Funktionen.

Aus Sicht der Programmierung werden die Iteratoren als implementiert als Thunks.

Um Iteratoren, Generatoren und Thread -Pools für die gleichzeitige Ausführung usw. als Thunks (auch anonyme Funktionen bezeichnet) zu implementieren, verwendet man Nachrichten, die an ein Schließobjekt gesendet werden und einen Dispatcher haben, und der Dispatcher antwortet auf "Nachrichten".

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

"nächste"Ist eine Nachricht, die an eine Schließung gesendet wird, die vom" erstellt wurde "Iter" Anruf.

Es gibt viele Möglichkeiten, diese Berechnung zu implementieren. Ich habe Mutation verwendet, aber es ist einfach, es ohne Mutation zu machen, indem ich den aktuellen Wert und den nächsten Kreislauf zurückgab.

Hier ist eine Demonstration, die die Struktur von R6Rs verwendet, aber die Semantik ist absolut identisch mit Pythons. Es ist das gleiche Berechnungsmodell, und es ist nur eine Änderung der Syntax erforderlich, um es in Python umzuschreiben.

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

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 sieht für mich aus yield Hält die Position des Programmflusses und die nächste Schleife von der "Ertragsposition". Es scheint, als würde es an dieser Position warten, und kurz davor funktioniert die Rückgabe eines Wertes draußen und funktioniert das nächste Mal weiter.

Es scheint eine interessante und schöne Fähigkeit zu sein: D.

Hier ist ein mentales Bild von was yield tut.

Ich betrachte einen Thread gerne einen Stapel (auch wenn er nicht so implementiert ist).

Wenn eine normale Funktion aufgerufen wird, setzt sie ihre lokalen Variablen auf den Stapel, löscht dann den Stapel und kehrt zurück. Die Werte seiner lokalen Variablen werden nie wieder gesehen.

Mit einer yield Funktion, wenn ihr Code ausgeführt wird (dh nach dem Aufrufen der Funktion, zurückgibt ein Generatorobjekt, dessen next() Die Methode wird dann aufgerufen), sie legt seine lokalen Variablen in ähnlicher Weise auf den Stapel und berechnet für eine Weile. Aber dann, wenn es die trifft yield An der Erklärung wird vor dem Räumungsteil des Stapels und der Rückkehr eine Momentaufnahme der lokalen Variablen vorgenommen und im Generatorobjekt gespeichert. Es schreibt auch den Ort auf, an dem es derzeit in seinem Code entspricht (dh der Besondere yield Aussage).

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

Wann next() Anschließend wird es als Sachen der Funktion auf den Stapel genannt und ruft sie erneut an. Die Funktion berechnet weiterhin von der Stelle, an der sie aufgehört hat, und ist sich der Tatsache nicht bewusst, dass sie gerade eine Ewigkeit in der Kältelagerung verbracht hat.

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 sehr unterschiedlich als die erste. Das yield Die Aussage mag nicht erreichbar sein, aber wenn sie irgendwo vorhanden ist, ändert sie die Natur dessen, was wir zu tun haben.

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

Berufung yielderFunction() Führen Sie seinen Code nicht aus, sondern macht einen Generator aus dem Code. (Vielleicht ist es eine gute Idee, solche Dinge mit dem zu nennen yielder Präfix für die Lesbarkeit.)

>>> 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']

Das gi_code und gi_frame In Feldern wird der gefrorene Staat gespeichert. Erforschung sie mit dir(..), Wir können bestätigen, dass unser mentales Modell oben glaubwürdig ist.

Wie jede Antwort schon sagt, yield wird zum Erstellen eines Sequenzgenerators verwendet. Es wird verwendet, um einige Sequenz dynamisch zu erzeugen. Zum Beispiel können Sie beim Lesen einer Dateizeile nach Zeile in einem Netzwerk die verwenden yield Funktion wie folgt:

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ührungssteuerung Gotcha

Die Ausführungssteuerung wird von getNextlines () auf die übertragen for Schleife, wenn Ausbeute ausgeführt wird. Jedes Mal, wenn GetNextlines () aufgerufen wird, beginnt die Ausführung von dem Punkt, an dem sie beim letzten Mal inne gemacht wurde.

Kurz gesagt, eine Funktion mit dem folgenden 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"

Ausbeute ist ein Objekt

EIN return In einer Funktion gibt ein einzelner Wert zurück.

Falls Sie es wollen eine Funktion, um eine große Reihe von Werten zurückzugeben, verwenden yield.

Wichtiger, yield ist ein Barriere.

Wie die Barriere in der CUDA -Sprache wird sie erst übertragen, wenn sie abgeschlossen ist.

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

Anschließend wird in jedem anderen Anruf die Schleife ausgeführt, die Sie noch einmal in der Funktion geschrieben haben, und gibt den nächsten Wert zurück, bis kein Wert zur Rückgabe mehr ist.

(Meine folgende Antwort spricht nur aus der Perspektive der Verwendung von Python -Generator, nicht aus der zugrunde liegende Implementierung des Generatormechanismus, die einige Tricks von Stapel- und Haufen Manipulation beinhalten.)

Wann yield wird anstelle von a verwendet return In einer Python -Funktion wird diese Funktion in etwas Besonderes genannt generator function. Diese Funktion gibt ein Objekt von zurück von generator Typ. Das yield Schlüsselwort ist ein Flag, um den Python -Compiler zur speziellen Behandlung zu benachrichtigen. Normale Funktionen beenden, sobald ein gewisser Wert daraus zurückgegeben wird. Aber mit Hilfe des Compilers der Generatorfunktion kann an gedacht werden als wiederauflösbar. Das heißt, der Ausführungskontext wird wiederhergestellt und die Ausführung wird vom letzten Lauf fortgesetzt. Bis Sie ausdrücklich die Rückkehr anrufen, was a erhöht StopIteration Ausnahme (die auch Teil des Iteratorprotokolls ist) oder das Ende der Funktion erreichen. Ich fand viele Referenzen darüber generator aber dieses eines von dem functional programming perspective ist der verdaulichste.

(Jetzt möchte ich über die Begründung dahinter sprechen generator, und die iterator Basierend auf meinem eigenen Verständnis. Ich hoffe, das kann Ihnen helfen, das zu erfassen Wesentliche Motivation Iterator und Generator. Ein solches Konzept zeigt sich auch in anderen Sprachen wie C#.)

Wie ich verstehe, speichern wir die Daten, wenn wir eine Reihe von Daten verarbeiten möchten, die Daten normalerweise irgendwo und dann nacheinander verarbeiten. Aber dieses naiv Ansatz ist problematisch. Wenn das Datenvolumen riesig ist, ist es teuer, sie als Ganzes vorher zu speichern. Also anstatt das zu speichern data selbst direkt, warum nicht eine Art aufbewahren? metadata Indirekt, dh the logic how the data is computed.

Es gibt 2 Ansätze, um solche Metadaten zu wickeln.

  1. Der OO -Ansatz wickeln wir die Metadaten ein as a class. Dies ist der sogenannte iterator wer implementiert das Iteratorprotokoll (dh das __next__(), und __iter__() Methoden). Dies ist auch der allgemein gesehen Iterator -Designmuster.
  2. Der funktionale Ansatz, wir wickeln die Metadaten ein as a function. Dies ist der sogenannte generator function. Aber unter der Motorhaube kehrte die zurück generator object still IS-A Iterator, weil es auch das Iteratorprotokoll implementiert.

In jedem Fall wird ein Iterator erstellt, dh ein Objekt, mit dem Sie die gewünschten Daten geben können. Der OO -Ansatz kann etwas komplex sein. Wie auch immer, welcher zu verwenden ist, liegt bei Ihnen.

Zusammenfassend lässt sich sagen yield Die Anweisung verwandelt Ihre Funktion in eine Fabrik, die ein spezielles Objekt namens a erzeugt generator Welches sich um den Körper Ihrer ursprünglichen Funktion wackiert. Wenn der generator wird iteriert, es wird Ihre Funktion ausgeführt, bis sie die nächste erreicht yield dann setzt die Ausführung aus und bewertet den an übergebenen Wert yield. Es wiederholt diesen Vorgang bei jeder Iteration, bis der Weg der Ausführung die Funktion verlässt. Zum Beispiel,

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

for i in simple_generator():
    print i

einfach ausgibt

one
two
three

Die Leistung kommt aus der Verwendung des Generators mit einer Schleife, die eine Sequenz berechnet. Der Generator führt die Schleife aus, die jedes Mal anhält, um das nächste Ergebnis der Berechnung zu erhalten. Auf diese Weise berechnet sie eine Liste im Fliegen, wobei der Vorteil der Speicher ist für besonders große Berechnungen gespeichert

Sagen Sie, Sie wollten eine eigene erstellen range Funktion, die eine iterable Reihe von Zahlen erzeugt, könnten Sie es so tun, so,

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

Aber das ist ineffizient, weil

  • Sie erstellen ein Array, das Sie nur einmal verwenden (dies verschwendet Speicher)
  • Dieser Code schlägt tatsächlich zweimal über dieses Array! :(

Glücklicherweise waren Guido und sein Team großzügig genug, um Generatoren zu entwickeln, damit 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

Jetzt bei jeder Iteration eine Funktion auf dem Generator genannt next() führt die Funktion aus, bis sie entweder eine "Ertrags -Anweisung" erreicht, in der sie stoppt und den Wert „ergibt“ oder das Ende der Funktion erreicht. In diesem Fall beim ersten Anruf,, next() führt bis zur Ertragserklärung aus und Rendite 'n', beim nächsten Anruf wird es die Increment -Anweisung ausführen, zurück zum 'while' Fahren Sie so fort, bis die Bedingung falsch zurückgibt und der Generator bis zum Ende der Funktion springt.

Viele Menschen benutzen return statt yield, aber in einigen Fällen yield Kann effizienter und einfacher zu arbeiten sein.

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

Rückkehr (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

Ertrag (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.

Aufrufen von 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 tun dasselbe, aber yield Verwendet drei Zeilen anstelle von fünf und hat eine weniger Variable, über die man sich Sorgen machen muss.

Dies ist das Ergebnis aus dem Code:

Output

Wie Sie sehen können, tun beide Funktionen dasselbe. Der einzige Unterschied ist return_dates() gibt eine Liste und yield_dates() gibt einem Generator.

Ein Beispiel im wirklichen Leben wäre so etwas wie das Lesen einer Dateizeile für Zeile oder wenn Sie nur einen Generator machen möchten.

yield ist wie ein Rückkehrelement für eine Funktion. Der Unterschied ist, dass die yield Element verwandelt eine Funktion in einen Generator. Ein Generator verhält sich genau wie eine Funktion, bis etwas „ergeben“ wird. Der Generator hält an, bis er als nächstes aufgerufen wird, und geht von genau dem gleichen Punkt fort wie begonnen. Sie können eine Sequenz aller Werte in einem "Ergebenen" Werten in einem erhalten, indem Sie anrufen list(generator()).

Das yield Das Schlüsselwort sammelt einfach zurückkehrende Ergebnisse. Denk an yield wie return +=

Hier ist ein einfaches yield Basierter Ansatz, um die Fibonacci -Serie zu berechnen, erklärte:

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

Wenn Sie dies in Ihre Replement eingeben und dann versuchen, es anzurufen, erhalten Sie ein mystifizierendes Ergebnis:

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

Dies liegt daran, dass das Vorhandensein von yield Python signalisiert, dass Sie a erstellen möchten Generator, das heißt ein Objekt, das Werte auf Bedarf erzeugt.

Wie generieren Sie diese Werte? Dies kann entweder direkt mit der integrierten Funktion erfolgen next, oder indirekt, indem es es an ein Konstrukt füttert, das Werte verbraucht.

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

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

Indirekt, wenn Sie bereitstellen fib zu einem for Schleife, a list Initialisierer, a tuple Initializer oder alles andere, das ein Objekt erwartet, das Werte erzeugt/erzeugt, "konsumieren" den Generator, bis keine Werte mehr von ihm erzeugt werden können (und er 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

Ebenso mit a tuple Initialisierer:

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

Ein Generator unterscheidet sich von einer Funktion in dem Sinne, dass er faul ist. Dies erfüllt dies, indem es den lokalen Zustand beibehält und Sie wieder aufnehmen können, wann immer Sie es brauchen.

Wenn Sie zum ersten Mal aufrufen fib durch nennen es:

f = fib()

Python kompiliert die Funktion, trifft auf die yield Schlüsselwort und gibt einfach ein Generatorobjekt zurück auf Sie zurück. Nicht sehr hilfreich, scheint es.

Wenn Sie dann den ersten Wert direkt oder indirekt anfordern, werden alle Anweisungen ausgeführt, die er findet, bis er auf a ist yield, dann ergibt es den Wert, dem Sie geliefert haben yield und pausiert. Für ein Beispiel, das dies besser demonstriert, verwenden wir einige print Anrufe (ersetzen durch print "text" Wenn auf 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 die Reply ein:

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

Sie haben ein Generatorobjekt, das jetzt auf einen Befehl wartet, der einen Wert generiert. Verwenden 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 zahlreichen Ergebnisse sind das, was gedruckt wird. Das zitierte Ergebnis ist das, aus dem zurückgegeben wird yield. Anruf next wieder jetzt:

>>> 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 erinnert sich, dass es innehalten wurde yield value und wieder aufnimmt von dort. Die nächste Nachricht wird gedruckt und die Suche nach der yield Aussage zur Pause, die erneut durchgeführt wird (aufgrund der while Schleife).

Ein einfaches Beispiel zu verstehen, was es 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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top