deutsch - yield python beispiel




Was macht das Keyword "Ertrag"? (20)

Was ist die Verwendung des yield Keyword in Python? Was tut es?

Zum Beispiel versuche ich, diesen Code 1 zu verstehen:

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


Was macht das yield Keyword in Python?

Antwort Gliederung / Zusammenfassung

  • Eine Funktion mit yield beim Aufruf einen Generator .
  • Generatoren sind Iteratoren, weil sie das Iteratorprotokoll implementieren, sodass Sie über sie iterieren können.
  • Einem Generator können auch Informationen gesendet werden , die ihn konzeptionell zu einer Coroutine machen .
  • In Python 3 können Sie mit yield from in beide Richtungen von einem Generator zu einem anderen delegieren .
  • (Anhang kritisiert einige Antworten, einschließlich der obersten, und erläutert die Verwendung von return in einem Generator.)

Generatoren:

yield ist nur innerhalb einer Funktionsdefinition zulässig, und die Einbeziehung von yield in eine Funktionsdefinition führt dazu, dass ein Generator zurückgegeben wird.

Die Idee für Generatoren stammt aus anderen Sprachen (siehe Fußnote 1) mit unterschiedlichen Implementierungen. In Python's Generators wird die Ausführung des Codes am Punkt des Ertrags frozen . Wenn der Generator aufgerufen wird (die Methoden werden unten erläutert), wird die Ausführung fortgesetzt und friert bei der nächsten Ausbeute ein.

yield das Iteratorprotokoll auf einfache Weise implementieren , 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 der abstrakten Iterator Basisklasse aus dem collections Modul prü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 Untertyp des Iterators:

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

Und wenn nötig, können wir so prüfen:

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

Ein Merkmal eines Iterator ist, dass er nach der Erschöpfung nicht mehr verwendet oder zurückgesetzt werden kann:

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

Sie müssen eine andere erstellen, wenn Sie die Funktionalität erneut verwenden möchten (siehe Fußnote 2):

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

Daten können programmgesteuert abgerufen werden, 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 in Python 2 verfügbar) können Sie die yield from :

def func(an_iterable):
    yield from an_iterable

Der yield from erlaubt jedoch auch die Delegation an Subgeneratoren, was im folgenden Abschnitt über kooperative Delegation mit Sub-Coroutines erläutert wird.

Coroutinen:

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

Hier ein Beispiel: Beachten Sie die received Variable, die auf die Daten zeigt, 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 anstellen. Abhängig von der verwendeten Python-Version wird die entsprechende __next__ oder __next__ Methode __next__ :

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

Und jetzt können wir Daten in den Generator senden. ( Senden von None ist das gleiche wie beim next Aufruf .):

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

Kooperative Delegation zu Sub-Coroutine mit yield from

Erinnern wir uns nun daran, dass yield from in Python 3 verfügbar ist. Dies ermöglicht uns, Coroutinen an eine Subcoroutine zu 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, der wie oben beschrieben von einem Generator verwendet werden kann:

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

Mehr über die genaue Semantik des yield from lesen Sie in PEP 380.

Andere Methoden: Schließen und werfen

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

>>> my_account.close()

Sie können auch eine Ausnahme auslösen, 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 abgedeckt:

Was macht das yield Keyword in Python?

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

Blinddarm:

Kritik der Spitze / akzeptierte Antwort **

  • Es ist verwirrend, was eine Iteration ermöglicht , nur anhand einer Liste. Siehe meine Referenzen oben, aber zusammenfassend: Ein Iterer hat eine __iter__ Methode, die einen Iterator __iter__ . Ein Iterator stellt eine .next (Python 2 oder .__next__ (Python 3)) .__next__ , die implizit von for Schleifen aufgerufen wird, bis sie StopIteration , und wenn dies der Fall ist, wird dies fortgesetzt.
  • Anschließend wird mit einem Generatorausdruck beschrieben, was ein Generator ist. Da ein Generator einfach ein praktischer Weg ist, einen Iterator zu erstellen, verwirrt er nur die Angelegenheit, und wir sind noch nicht zum yield .
  • Beim Steuern einer Generator-Erschöpfung ruft er die .next Methode auf, wenn er stattdessen die eingebaute Funktion verwenden soll. Es wäre eine geeignete Indirektionsebene, da sein Code in Python 3 nicht funktioniert.
  • Itertools? Dies war für die yield überhaupt nicht relevant.
  • Keine Erörterung der Methoden, die yield liefert zusammen mit der neuen Funktionalität in Python 3. Die Antwort von oben / akzeptiert ist eine sehr unvollständige Antwort.

Antwortkritik, die den yield in einem Generatorausdruck oder -verständnis suggeriert.

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 der Ertrag ein Ausdruck ist, wurde es von manchen als interessant angepriesen, ihn in Verständnis- oder Generatorausdrücken zu verwenden - obwohl er keinen besonders guten Anwendungsfall zitiert.

Die CPython-Kernentwickler diskutieren über die Abschreibung ihrer Zulage . Hier ist ein relevanter Beitrag aus der Mailingliste:

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

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

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

Meine Stimme ist ein SyntaxError, da Sie nicht das bekommen, was Sie von der Syntax erwarten.

Ich bin damit einverstanden, dass dies ein vernünftiger Ort für uns ist, da jeder Code, der auf das aktuelle Verhalten angewiesen ist, wirklich zu schlau ist, um unterhalten zu werden.

In Bezug auf die Anreise wollen wir wahrscheinlich:

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

Prost, Nick.

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

Des Weiteren gibt es eine noch offene Frage (10544) , die in Richtung dieser Hinweis zu sein scheint nie zu sein eine gute Idee (ist bereits Anhebung Warnungen Syntax PyPy, eine Python - Implementierung in Python geschrieben.)

Unterm Strich, bis uns die Entwickler von CPython etwas anderes sagen: Geben Sie keinen yieldGeneratorausdruck oder -verständnis ein.

Die returnAussage in einem Generator

In Python 2 :

In einer Generatorfunktion returndarf die Anweisung keine einschließen expression_list. In diesem Zusammenhang bedeutet ein bloßes returnZeichen, dass der Generator fertig ist und StopIterationhochgefahren wird.

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

In Python 3 :

In einer Generatorfunktion zeigt die returnAnweisung an, dass der Generator fertig ist und StopIterationausgelöst wird. Der zurückgegebene Wert (falls vorhanden) wird als Argument zum Konstruieren verwendet StopIterationund wird zum StopIteration.valueAttribut.

Fußnoten

  1. Auf die Sprachen CLU, Sather und Icon wurde in dem Vorschlag verwiesen, um das Konzept von Generatoren in Python einzuführen. Die allgemeine Idee ist, dass eine Funktion den internen Zustand aufrechterhalten und auf Anforderung des Benutzers Zwischen-Datenpunkte liefern kann. Dies versprach eine überlegene Leistung gegenüber anderen Ansätzen, einschließlich Python-Threading , das auf manchen Systemen nicht einmal verfügbar ist.

  2. Dies bedeutet beispielsweise, dass xrangeObjekte ( rangein Python 3) keine Iterators sind, obwohl sie iterierbar sind, da sie wiederverwendet werden können. Wie Listen geben ihre __iter__Methoden Iteratorobjekte zurück.

  3. yieldwurde ursprünglich als Anweisung eingeführt, was bedeutet, dass sie nur am Anfang einer Zeile in einem Codeblock erscheinen konnte. Erzeugt jetzt yieldeinen Ertragsausdruck. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Diese Änderung wurde vorgeschlagen , damit ein Benutzer Daten in den Generator senden kann, so wie er sie empfangen könnte. Um Daten zu senden, muss man sie in der Lage sein, etwas zuzuweisen, und dazu funktioniert eine Anweisung einfach nicht.


Grokking zum Grokking yield

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

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

Dieser Trick vermittelt zwar einen Eindruck von der Logik der Funktion, aber was mit yield tatsächlich geschieht, unterscheidet sich erheblich von dem, was in der Liste vorkommt. In vielen Fällen wird der Ertragsansatz viel speichereffizienter und auch schneller sein. In anderen Fällen werden Sie durch diesen Trick in einer Endlosschleife hängen bleiben, obwohl die ursprüngliche Funktion einwandfrei funktioniert. Lesen Sie weiter, um mehr zu erfahren ...

Verwechsle nicht deine Iterables, Iteratoren und Generatoren

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 :

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

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

  2. Verwendet den Iterator, um Elemente zu durchlaufen:

    Rufen Sie die next() -Methode weiterhin 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() StopIteration wird, bedeutet dies, dass der Iterator keine weiteren Werte enthält und die Schleife beendet wird.

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

Hier ist mylist eine Iteration, weil sie 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, sowohl __iter__() als auch next() in derselben Klasse zu implementieren und __iter__() self . 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 welche Art von Objekt es sich handelt - sie folgt lediglich dem Iteratorprotokoll und ist froh, wenn Sie item (nach dem next() . Integrierte Listen geben ihre Elemente einzeln zurück, Wörterbücher geben die Tasten einzeln zurück, Dateien geben die Zeilen einzeln wieder usw. zurück. Und Generatoren kehren zurück.

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Wenn Sie in f123() drei return Anweisungen hatten, f123() nur die erste ausgeführt und die Funktion beendet. Aber f123() ist keine gewöhnliche Funktion. Wenn f123() aufgerufen wird, gibt es keinen 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 Generator-Objekt zu überlaufen, wird die Funktion nach der zuvor zurückgegebenen yield bereits in der nächsten Zeile 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 so lange, bis die Funktion beendet ist. An diesem Punkt hebt der Generator die StopIteration an und die Schleife wird beendet.

Das Generator-Objekt ist also __iter__() ein Adapter - an einem Ende weist es das Iterator-Protokoll auf, indem es die __iter__() und next() __iter__() , 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. Dies funktioniert nicht in allen Fällen, z. B. wenn Sie Endlosschleifen haben, oder es nutzt den Speicher möglicherweise ineffizient, wenn Sie eine sehr lange Liste haben. Der andere Ansatz besteht darin, eine neue iterable-Klasse SomethingIter zu implementieren, die den Status in Instanzmitgliedern behält und den nächsten logischen Schritt in ihrer next() (oder __next__() in Python 3) -Methode __next__() . 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.


Denk darüber so:

Ein Iterator ist nur ein fantastisch klingender Begriff für ein Objekt, das eine next () - Methode hat. Eine nachgebende Funktion ist also so etwas:

Originalfassung:

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

for i in some_function():
    print i

Grundsätzlich macht der Python-Interpreter mit dem obigen Code Folgendes:

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 Vorgänge hinter den Kulissen for erfahren, kann die for Schleife folgendermaßen umgeschrieben werden:

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

Ist das sinnvoller oder verwirrt man Sie einfach mehr? :)

Ich sollte darauf hinweisen, dass dies zu Illustrationszwecken eine Vereinfachung darstellt. :)


Um zu verstehen, was yield bringt, 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 " for... in... " verwenden können, ist ein iterierbares Element. lists , strings , Dateien ...

Diese iterablen Funktionen sind praktisch, weil Sie sie so oft 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 Iterer, den Sie nur einmal durchlaufen können . Generatoren speichern nicht alle Werte im Speicher, sie generieren die Werte im laufenden Betrieb :

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

Es ist genau dasselbe, außer dass Sie () anstelle von [] . ABER, Sie können for i in mygenerator kein zweites Mal durchfü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 , außer dass die Funktion einen Generator zurückgibt.

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

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 den yield zu beherrschen, müssen Sie verstehen, dass der Code, den Sie in den Funktionshauptteil geschrieben haben , beim Aufruf der Funktion nicht ausgeführt wird. Die Funktion gibt nur das Generatorobjekt zurück, das ist etwas knifflig :-)

Dann wird Ihr Code jedes Mal ausgeführt, wenn der Generator den Generator verwendet.

Nun der schwierige Teil:

Beim ersten Aufruf von for das Generatorobjekt, das von Ihrer Funktion erstellt wurde, wird der Code in Ihrer Funktion vom Anfang bis zum Aufruf von yield . 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, wenn die Funktion ausgeführt wird, trifft jedoch nicht mehr auf den yield . Das kann daran liegen, dass die Schleife zu Ende gegangen ist oder Sie ein "if/else" nicht mehr befriedigen.

Ihr Code erklärt

Generator:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if 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, erstellt jedoch while neue Generatorobjekte, die andere als die vorherigen generieren, da sie nicht auf dieselben Werte angewendet werden Knoten.

  • Die extend() -Methode ist eine Listenobjektmethode, die eine Iteration erwartet und ihre Werte zur Liste hinzufügt.

Normalerweise übergeben wir 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:

Kontrolle der Generatorerschöpfung

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

Hinweis: Verwenden Sie für Python 3 print(corner_street_atm.__next__()) 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 Iterationen. Wollten Sie schon immer einen Generator kopieren? Kette zwei Generatoren? Werte in einer verschachtelten Liste mit einem Einzeiler gruppieren? Map / Zip ohne eine andere Liste zu erstellen?

Dann import itertools 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 __iter__() (Implementieren der __iter__() Methode) und Iteratoren (Implementieren der __next__() Methode) __next__() . 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 Schleifen funktionieren .


yieldist wie ein Rückgabeelement für eine Funktion. Der Unterschied ist, dass das yieldElement eine Funktion in einen Generator umwandelt. Ein Generator verhält sich wie eine Funktion, bis etwas "nachgebend" ist. 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 durch Aufruf aufrufen list(generator()).


Der Ertrag ist ein Objekt

Ein returnin 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 yieldist eine Barriere .

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

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

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.


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

Ich nehme an, Sie haben die returnAussage gelernt .

Als Analogie returnund yieldsind Zwillinge. returnbedeutet "Rückkehr und Stopp", während "Rendite" "Rückkehr, aber weiter" bedeutet

  1. Versuche eine num_list mit zu bekommen 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 Nummer und keine Liste. returnlässt Sie nie glücklich durchsetzen, nur einmal einführen und beenden.

  1. Da kommt yield

Ersetzen returndurch 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 returndem, der einmal ausgeführt wird und stoppt, werden yielddie von Ihnen geplanten Zeiten ausgeführt. Sie können returnals return one of them, und yieldals interpretieren return all of them. Das heißt iterable.

  1. Ein weiterer Schritt, mit dem wir die yieldAnweisung umschreiben könnenreturn
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 geht um den Kern yield.

Der Unterschied zwischen einer Listenausgabe returnund der Objektausgabe yieldist:

Sie erhalten immer [0, 1, 2] von einem Listenobjekt, konnten sie jedoch nur einmal von der Objektausgabe abrufen yield. Es hat also ein neues generatorNamensobjekt, wie in angezeigt Out[11]: <generator object num_list at 0x10327c990>.

Abschließend als Metapher, um es zu grokieren:

  • returnund yieldsind Zwillinge
  • listund generatorsind Zwillinge

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 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 einen iterierbaren Wert (etwa) bis 50 Milliarden zählen, nicht bis 50 Milliarden zählen sofort und speichern Sie die 50 Milliarden Zahlen, um durchzuzählen. Wieder ist dies ein ziemlich erfundenes 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 mit ihm effiziente Permutationen geschrieben werden. Verwenden Sie Yield, um Dinge durch den Aufrufstapel zu verschieben, anstatt eine Art Stapelvariable zu verwenden. Generatoren können auch für die spezialisierte Baumdurchquerung und für andere Zwecke verwendet werden.


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 yielddie Position des Programmflusses zu halten und die nächste Schleife beginnt von der Position "Rendite". 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


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

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

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


(Meine Antwort unten bezieht sich nur auf die Verwendung des Python-Generators, nicht auf die zugrunde liegende Implementierung des Generatormechanismus , der einige Tricks der Stapel- und Heap-Manipulation beinhaltet.)

Wenn yieldanstelle von returnin einer Python-Funktion verwendet wird, wird diese Funktion in einen speziellen Aufruf umgewandelt generator function. Diese Funktion gibt ein Objekt vom generatorTyp zurück. Das yieldSchlüsselwort ist ein Flag, um den Python-Compiler zu benachrichtigen, um eine solche Funktion speziell zu behandeln. Normale Funktionen werden beendet, sobald ein Wert zurückgegeben wird. Mit Hilfe des Compilers kann die Generatorfunktion jedoch als wiederaufgenommen betrachtet werden. Das heißt, der Ausführungskontext wird wiederhergestellt und die Ausführung wird ab der letzten Ausführung fortgesetzt. Bis Sie explizit return aufrufen, wird eine StopIterationAusnahme ausgelöst (die auch Teil des Iterator-Protokolls ist) oder das Ende der Funktion erreicht. Ich fand eine Menge von Referenzen über , generatoraber diese onevon dem functional programming perspectiveist das verdaulichste.

(Jetzt möchte ich über die Beweggründe sprechen generator, und das iteratorbasiert auf meinem eigenen Verständnis. Ich hoffe , das Sie erfassen helfen können wesentliche Motivation der Iterator und Generator. Ein solches Konzept zeigt sich auch in anderen Sprachen auf wie C #) .

Ich verstehe, wenn wir ein Bündel von Daten verarbeiten wollen, speichern wir die Daten normalerweise erst irgendwo und verarbeiten sie dann einzeln. Dieser intuitive Ansatz ist jedoch problematisch. Wenn das Datenvolumen sehr groß ist, ist es teuer, sie zuvor als Ganzes zu speichern. Also, anstatt sich dataselbst direkt zu speichern, warum nicht irgendeine Art metadataindirekt speichern , dhthe logic how the data is computed .

Es gibt zwei Ansätze, um solche Metadaten einzuwickeln.

  1. Beim OO-Ansatz wickeln wir die Metadaten ein as a class. Dies ist der sogenannte iteratorwho, der das Iterator-Protokoll (dh die Methoden __next__()und und __iter__()) implementiert . Dies ist auch das häufig gesehene Iterator-Entwurfsmuster .
  2. Beim funktionalen Ansatz wickeln wir die Metadaten ein as a function. Dies ist das sogenannte generator function. Aber unter der Haube, die zurück generator objectnoch IS-Aiterator , weil es auch das Iterator - Protokoll implementiert.

In jedem Fall wird ein Iterator erstellt, dh ein Objekt, das Ihnen die gewünschten Daten liefern kann. Der OO-Ansatz kann etwas komplex sein. Wie auch immer, welches Sie verwenden, liegt bei Ihnen.


Aus Programmiersicht werden 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 wird, die durch den 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
->

Es gibt eine Art von Antwort, die meiner Meinung nach noch nicht gegeben wurde, unter den vielen großartigen Antworten, die beschreiben, wie Generatoren verwendet werden. Hier ist die Antwort auf die Programmiersprachentheorie:

Die yieldAnweisung in Python gibt einen Generator zurück. Ein Generator in Python ist eine Funktion, die Fortsetzungen zurückgibt (und insbesondere eine Art Coroutine, Fortsetzungen stellen jedoch den allgemeineren Mechanismus dar, um zu verstehen, was vor sich geht).

Fortsetzungen in der Theorie der Programmiersprachen sind eine wesentlich grundlegendere Art der Berechnung, sie werden jedoch nicht oft verwendet, da sie extrem schwer zu verstehen und auch sehr schwer zu implementieren sind. Aber die Vorstellung von einer Fortsetzung ist unkompliziert: Es ist der Zustand einer Berechnung, der noch nicht abgeschlossen ist. In diesem Zustand werden die aktuellen Werte der Variablen, die noch auszuführenden Operationen usw. gespeichert. Später im Programm kann dann die Fortsetzung aufgerufen werden, so dass die Programmvariablen auf diesen Zustand zurückgesetzt werden und die gespeicherten Operationen ausgeführt werden.

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

In Continuation Passing Style (CPS) sind Fortsetzungen normale Funktionen (nur in Sprachen, in denen Funktionen erstklassig sind), die der Programmierer explizit verwaltet und an Unterprogramme weitergibt. In diesem Stil wird der Programmstatus durch Schließungen (und die darin verschlüsselten Variablen) und nicht durch irgendwo auf dem Stapel befindliche Variablen dargestellt. Funktionen, die den Steuerfluss verwalten, akzeptieren die Fortsetzung als Argumente (in einigen Variationen von CPS akzeptieren Funktionen möglicherweise mehrere Fortsetzungen) und manipulieren den Steuerfluss, indem sie aufgerufen werden, indem sie einfach aufgerufen und anschließend zurückgegeben werden. Ein sehr einfaches Beispiel für einen Weiterleitungsstil ist:

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 das eigentliche Schreiben der Datei in einer Fortsetzung (was möglicherweise eine sehr komplexe Operation mit vielen zu schreibenden Details sein kann) und übergibt dann diese Fortsetzung (dh als erstes). Klassenabschluss) an einen anderen Operator, der einige weitere Verarbeitungsschritte ausführt und diese dann bei Bedarf aufruft. (Ich verwende dieses Entwurfsmuster häufig in der eigentlichen GUI-Programmierung, entweder weil mir Codezeilen erspart werden oder, was noch wichtiger ist, der Steuerungsfluss nach dem Auslösen von GUI-Ereignissen verwaltet wird.)

Der Rest dieses Beitrags wird Fortsetzungen ohne Verlust der Allgemeinheit als CPS konzeptualisieren, da er viel einfacher zu verstehen und zu lesen ist.


Sprechen wir jetzt über Generatoren in Python. Generatoren sind ein spezifischer Subtyp der Fortsetzung. Während Fortsetzungen im Allgemeinen den Zustand einer Berechnung (dh den Aufrufstack des Programms) speichern können , können Generatoren nur den Iterationszustand über einen Iterator speichern . Obwohl diese Definition für bestimmte Anwendungsfälle von Generatoren etwas irreführend ist. Zum Beispiel:

def f():
  while True:
    yield 4

Dies ist eindeutig ein vernünftiges iterierbares Verhalten, dessen Verhalten genau definiert ist - jedes Mal, wenn der Generator über sie iteriert, gibt er 4 zurück (und dies für immer). Es ist jedoch wahrscheinlich nicht der prototypische Typ von Iterer, der mir einfällt, wenn ich an Iteratoren (dh for x in collection: do_something(x)) denke . Dieses Beispiel veranschaulicht die Leistung von Generatoren: Wenn etwas ein Iterator ist, kann ein Generator den Zustand seiner Iteration speichern.

Wiederholen: Fortsetzungen können den Status des Programmstapels speichern, und Generatoren können den Iterationsstatus speichern. Das bedeutet, dass Fortsetzungen wesentlich leistungsfähiger sind als Generatoren, aber auch Generatoren sind viel einfacher. Sie sind für den Sprachdesigner leichter zu implementieren und für den Programmierer einfacher zu verwenden (wenn Sie etwas Zeit zum Brennen haben, versuchen Sie, diese Seite mit den Fortsetzungen und dem Aufruf / cc zu lesen und zu verstehen ).

Sie können Generatoren jedoch leicht als einfachen, spezifischen Fall des Weiterleitungs-Übergabestils implementieren (und konzeptualisieren):

Bei jedem yieldAufruf wird die Funktion aufgefordert, eine Fortsetzung zurückzugeben. Wenn die Funktion erneut aufgerufen wird, beginnt sie dort, wo sie aufgehört hat. Im Pseudo-Pseudocode (dh nicht im Pseudocode, aber nicht im Code) nextlautet die Methode des Generators grundsätzlich 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

wobei das yieldSchlüsselwort eigentlich syntaktischer Zucker für die echte Generatorfunktion ist, 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 ein Pseudocode ist und die tatsächliche Implementierung von Generatoren in Python komplexer ist. Um zu verstehen, was los ist, versuchen Sie, einen Weiterleitungsstil zu verwenden, um Generatorobjekte ohne Verwendung des yieldSchlüsselworts zu implementieren .


Es gibt eine andere yieldVerwendung und Bedeutung (seit Python 3.3):

yield from <expr>

Ab 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) Folgendes einführen:

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

um zu vermeiden, dass Coroutinen mit einem normalen Generator verwechselt werden (wird heute yieldin beiden verwendet).


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.


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

Hier ist ein einfacher yieldAnsatz, um die Fibonacci-Reihe zu berechnen:

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 REPL eingeben und dann versuchen, es aufzurufen, erhalten Sie ein rätselhaftes Ergebnis:

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

Dies liegt daran, dass das Vorhandensein von yieldPython signalisiert wurde, dass Sie einen Generator erstellen möchten , d. H. Ein Objekt, das bei Bedarf Werte generiert.

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

Mit der integrierten next()Funktion rufen Sie .next/ direkt auf __next__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 fibeine forSchleife, einen listInitialisierer, einen tupleInitialisierer oder etwas anderes bereitstellen , das ein Objekt erwartet, das Werte erzeugt / erzeugt, "verbrauchen" Sie den Generator, bis keine weiteren Werte mehr erzeugt werden können (und er zurückgegeben wird). :

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

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

Beim ersten Aufruf fibdurch Aufrufen:

f = fib()

Python kompiliert die Funktion, stößt auf das yieldSchlüsselwort 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 a stößt yield, und gibt dann den Wert zurück, den Sie angegeben haben, yieldund hält an. Für ein Beispiel, das dies besser veranschaulicht, verwenden wir einige printAufrufe (ersetzen Sie durch print "text"if 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 in der REPL ein:

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

Sie haben jetzt ein Generatorobjekt, das darauf wartet, dass ein Befehl einen Wert generiert. Verwenden Sie nextund 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 zurückgegeben wird yield. Rufen Sie nextjetzt erneut an:

>>> 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 daran, dass er angehalten wurde, yield valueund fährt von dort fort. Die nächste Nachricht wird gedruckt und die Suche nach der yieldAnweisung, die angehalten werden soll, wird erneut ausgeführt (aufgrund der whileSchleife).


Hier ist ein mentales Bild von dem, was yieldtut.

Ich stelle mir einen Thread als einen Stapel vor (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 es wird zurückgegeben. Die Werte seiner lokalen Variablen werden nie wieder angezeigt.

yieldWenn bei einer Funktion der Code zu laufen beginnt (dh nachdem die Funktion aufgerufen wurde, ein Generatorobjekt zurückgegeben wird, dessen next()Methode dann aufgerufen wird), werden ihre lokalen Variablen auf ähnliche Weise auf den Stack gelegt und eine Weile berechnet. Wenn er jedoch die yieldAnweisung trifft , bevor er seinen Teil des Stapels löscht und zurückgibt, erstellt er eine Momentaufnahme seiner lokalen Variablen und speichert sie im Generatorobjekt. Sie schreibt auch den Ort auf, an dem sie sich aktuell im Code befindet (dh die bestimmte yieldAnweisung).

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

Wenn next()er anschließend aufgerufen wird, ruft er den Inhalt der Funktion auf den Stack ab und animiert ihn 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 yieldAussage mag unerreichbar sein, aber wenn sie irgendwo vorhanden ist, ändert sie die Art, mit der wir es zu tun haben.

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

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

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

Die gi_codeund gi_frameFelder sind , wo der gefrorene Zustand gelagert wird. Wenn dir(..)wir sie untersuchen , können wir bestätigen, dass unser oben beschriebenes Denkmodell glaubwürdig ist.


Während viele Antworten zeigen, warum Sie yieldeinen Generator zum Erstellen eines Generators verwenden, gibt es mehr Möglichkeiten für yield. Es ist ziemlich einfach, eine Coroutine zu erstellen, die die Weitergabe von Informationen zwischen zwei Codeblöcken ermöglicht. Ich werde keines der schönen Beispiele wiederholen, die bereits für die yieldErstellung eines Generators genannt wurden.

Um zu verstehen, was a yieldim folgenden Code bewirkt, können Sie den Zyklus durch einen beliebigen Code mit einem Finger verfolgen yield. Jedes Mal, wenn Ihr Finger auf den Finger schlägt yield, müssen Sie warten, bis ein nextoder ein sendEintrag erfolgt. Wenn a nextaufgerufen wird, verfolgen Sie den Code, bis Sie auf yield... klicken. Der Code rechts von yieldwird ausgewertet und an den Anrufer zurückgegeben. Dann warten Sie. Wenn nexterneut aufgerufen wird, führen Sie eine weitere Schleife durch den Code. Sie werden jedoch feststellen, dass in einer Coroutine yieldauch eine send… verwendet werden kann, die einen Wert vom Aufrufer in die nachgiebige Funktion sendet . Wenn a sendgegeben ist, dannyieldempfängt den gesendeten Wert und spuckt ihn auf der linken Seite aus… dann wird der Trace durch den Code fortgesetzt, bis Sie erneut auf die yieldSchaltfläche drücken (den Wert am Ende zurückgeben, als ob er nextaufgerufen 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()

Wie jede Antwort vermuten lässt, yieldwird ein Sequenzgenerator erstellt. Es wird zum dynamischen Erzeugen einer Sequenz verwendet. Wenn Sie z. B. eine Datei zeilenweise in einem Netzwerk lesen, können Sie die yieldFunktion 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 forSchleife übergeben, wenn Yield 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"




coroutine