Es gibt eine Art von Antwort, die meiner Meinung nach noch nicht gegeben wurde, unter den vielen großartigen Antworten, die beschreiben, wie man Generatoren verwendet. Hier ist die programmiersprachtheoretische Antwort:
Le site yield
Anweisung in Python gibt einen Generator zurück. Ein Generator in Python ist eine Funktion, die Folgendes zurückgibt Fortsetzungen (und speziell eine Art von Coroutine, aber Fortsetzungen stellen den allgemeineren Mechanismus dar, um zu verstehen, was vor sich geht).
Fortsetzungen in der Programmiersprachentheorie sind eine viel grundlegendere Art der Berechnung, werden aber nicht oft verwendet, weil sie extrem schwer zu begründen und auch sehr schwer zu implementieren sind. Aber die Idee, was eine Fortsetzung ist, ist einfach: Es ist der Zustand einer Berechnung, die noch nicht beendet ist. In diesem Zustand werden die aktuellen Werte der Variablen, die noch auszuführenden Operationen usw. gespeichert. Zu einem späteren Zeitpunkt im Programm kann die Fortsetzung dann aufgerufen werden, so dass die Variablen des Programms in diesen Zustand zurückgesetzt und die gespeicherten Operationen ausgeführt werden.
Fortsetzungen, in dieser allgemeineren Form, können auf zwei Arten implementiert werden. In der call/cc
Auf diese Weise wird der Stack des Programms buchstäblich gespeichert, und wenn die Fortsetzung aufgerufen wird, wird der Stack wiederhergestellt.
Im Continuation-Passing-Stil (CPS) sind Fortsetzungen ganz normale Funktionen (nur in Sprachen, in denen Funktionen erste Klasse sind), die der Programmierer explizit verwaltet und an Unterprogramme weitergibt. In diesem Stil wird der Programmzustand durch Closures (und die darin kodierten Variablen) dargestellt und nicht durch Variablen, die sich irgendwo auf dem Stack befinden. Funktionen, die den Kontrollfluss verwalten, akzeptieren Fortsetzungen als Argumente (in einigen Varianten von CPS können Funktionen mehrere Fortsetzungen akzeptieren) und manipulieren den Kontrollfluss, indem sie einfach aufgerufen werden und anschließend zurückkehren. Ein sehr einfaches Beispiel für die Weitergabe von Fortsetzungen ist das folgende:
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 vereinfachten) Beispiel speichert der Programmierer den Vorgang des eigentlichen Schreibens der Datei in einer Fortsetzung (was potenziell ein sehr komplexer Vorgang mit vielen auszuschreibenden Details sein kann) und übergibt dann diese Fortsetzung (d. h. als erstklassige Schließung) an einen anderen Operator, der weitere Verarbeitungsschritte durchführt und sie dann bei Bedarf aufruft. (Ich verwende dieses Entwurfsmuster häufig in der GUI-Programmierung, entweder weil es mir Codezeilen spart oder, was noch wichtiger ist, um den Kontrollfluss nach dem Auslösen von GUI-Ereignissen zu verwalten).
Im weiteren Verlauf dieses Beitrags werden Fortsetzungen als CPS konzeptualisiert, da dies sehr viel einfacher zu verstehen und zu lesen ist.
Lassen Sie uns nun über Generatoren in Python sprechen. Generatoren sind ein spezieller Untertyp von Fortsetzungen. Während Fortsetzungen sind im Allgemeinen in der Lage, den Zustand einer Berechnung (d.h. der Aufrufstapel des Programms), Generatoren sind nur in der Lage, den Zustand der Iteration über eine Iterator . Allerdings ist diese Definition für bestimmte Anwendungsfälle von Generatoren etwas irreführend. Zum Beispiel:
def f():
while True:
yield 4
Dies ist eindeutig eine vernünftige Iterable, deren Verhalten gut definiert ist - jedes Mal, wenn der Generator darüber iteriert, gibt er 4 zurück (und tut dies für immer). Aber es ist wahrscheinlich nicht der prototypische Typ von Iterablen, der einem in den Sinn kommt, wenn man an Iteratoren denkt (d.h., for x in collection: do_something(x)
). Dieses Beispiel verdeutlicht die Leistungsfähigkeit von Generatoren: Wenn etwas ein Iterator ist, kann ein Generator den Zustand seiner Iteration speichern.
Um es noch einmal zu wiederholen: Fortsetzungen können den Zustand des Stapels eines Programms speichern, während Generatoren den Zustand der Iteration speichern können. Das bedeutet, dass Fortsetzungen sehr viel leistungsfähiger sind als Generatoren, aber auch, dass Generatoren viel, viel einfacher sind. Sie sind für den Sprachdesigner einfacher zu implementieren und für den Programmierer einfacher zu benutzen (wenn Sie etwas Zeit haben, lesen und verstehen Sie diese Seite über Fortsetzungen und call/cc ).
Aber man könnte Generatoren leicht als einen einfachen, spezifischen Fall von Continuation-Passing-Stil implementieren (und konzeptualisieren):
Wann immer yield
aufgerufen wird, teilt sie der Funktion mit, dass sie eine Fortsetzung zurückgeben soll. Wenn die Funktion erneut aufgerufen wird, beginnt sie dort, wo sie aufgehört hat. In Pseudo-Pseudocode (d. h. nicht Pseudocode, aber auch nicht Code) ist der Generator also next
Methode ist im Wesentlichen 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
wo die yield
Schlüsselwort ist eigentlich syntaktischer Zucker für die eigentliche Generatorfunktion, im Grunde 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. Aber als Übung, um zu verstehen, was vor sich geht, versuchen Sie, Generatorobjekte im Stil der Weitergabe von Fortsetzungen zu implementieren, ohne die yield
Stichwort.