Es gibt andere Seiten, die Python-Generatoren erklären, aber es gab viele, die auf Englisch waren oder die sich für mich nicht richtig anfühlten. Deshalb erklärte ich die Generatoren auf meine eigene Weise, auch um meine Gedanken zu organisieren. Ich werde versuchen. Wenn ich mich weigere, bin ich im Grunde verwirrt, also könnte ich etwas Falsches sagen. Wenn Sie Fehler haben, kommentieren Sie diese bitte und es wird eine große Hilfe für Sie sein, um zu lernen. Die hier vorgestellte Verwendung des Generators ist nur ein paar Anwendungsbeispiele und bedeutet nicht, dass es keine andere Verwendung gibt.
Es gibt andere großartige Artikel für detaillierte und genaue Erklärungen. Bitte beziehen Sie sich auf diese: Python Iterator und Generator
Um es kurz auszudrücken, stellen Sie es sich als "normale Funktion vor, bei der die return-Anweisung durch profit ersetzt wird". (Es ist anders, aber bitte verpassen Sie es vorerst). Wenn Sie Yield anstelle von Return schreiben, gibt ** nur ein Aufruf keinen Wert ** zurück. Stattdessen wird es mit einer for-Anweisung usw. aufgerufen und ** gibt Werte nacheinander zurück **. Es ist wahrscheinlich einfacher zu verstehen, wenn man ein Beispiel gibt.
example.py
def count_up():
x = 0
while True:
yield x
x += 1
Sicherlich ist es Ertrag, nicht Rückkehr. Und nach * Ausbeute * passiert etwas. Da nach der return-Anweisung keine Verarbeitung erfolgt, können Sie sofort erkennen, dass es sich um etwas anderes handelt, als nur die Rückgabe einer normalen Funktion zu ersetzen. Ich werde es viel erklären. Derzeit ruft dieser Generator zum Beispiel auf:
>>> countup()
<generator object countup at 0x101fa0468>
>>> for i in countup():
... print(i)
... if i == 5:
... break
0
1
2
3
4
5
Sicherlich scheint es, dass kein Wert nur durch Aufrufen zurückgegeben wird. Beim Aufruf einer for-Anweisung werden die Werte stattdessen nacheinander zurückgegeben. Zu diesem Zeitpunkt gibt count_up bei jedem Aufruf den Wert von x zurück, erhöht jedoch bei jedem Aufruf den Wert von x. Dies ist, was der Prozess nach der Ausbeute kommt. Jetzt, da Sie wissen, was der Generator ist (keine Sorge, ich gebe Ihnen einige Beispiele, wenn Sie sich nicht sicher sind). Aber woran Sie hier wahrscheinlich denken, ist ** Wo benutzt du das? ** ** ** Ich denke, das bedeutet es. Dieses Mal werde ich den Zweck des Generators auf meine eigene Weise erklären.
Wie Sie aus dem vorherigen Beispiel sehen können, sind Generator und Funktion ** völlig unterschiedlich **. Generatoren sind ** Klassen ** überwiegend näher als Funktionen. Zum Beispiel im vorherigen Beispiel
>>> countup()
Kann klar verstanden werden, wenn interpretiert wird, dass eine Instanz der Klasse erstellt wurde. Der folgende Code funktioniert also auch:
>>> y = countup()
>>> for i in y:
... print(i)
... if i == 5:
... break
0
1
2
3
4
5
Wenn Sie danach den folgenden Prozess ausführen,
>>> for i in y:
... print(i)
... if i == 10:
... break
6
7
8
9
10
0 bis 5 sind verschwunden! Der Grund ist, dass sich der Generator bei jedem Aufruf ** daran erinnert, was getan wurde **. Mit anderen Worten, es hat einen ** Zustand **. Dies ist der größte Unterschied zur Funktion. Im obigen Beispiel ist x = 6 in y, wenn die erste for-Anweisung endet, und dies wird bis zur nächsten for-Anweisung fortgesetzt.
Warum ist das so wichtig? Betrachten wir zum Beispiel die Szene der Berechnung der Fibonacci-Zahlenfolge, die jeder liebt. Eine häufig eingeführte Berechnungsmethode unter Verwendung von Wiederholung ist wie folgt:
fibonacci_recursive.py
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n-1) + fibonacci(n-2)
Es wäre schön, wenn n klein wäre, aber wenn n groß wäre, würde sich der Stapel füllen und ein Fehler würde ausgelöst. Auf der anderen Seite bei Verwendung eines Generators
fibonacci_generator.py
def fibonacci():
a, b = 0, 1
while 1:
yield b
a, b = b, a+b
Auf diese Weise wird es möglich, die Fibonacci-Sequenz zu berechnen, ohne den Stapel zu verschwenden, indem die Werte der letzten beiden Zeichenfolgen als Zustände gehalten werden.
Wenn man sich nur die bisherigen Beispiele ansieht, denkt der Leser wahrscheinlich ** Ist List nicht gut? ** ** ** Ich denke, das bedeutet es. Wenn Sie nur von Grund auf neu drucken möchten, können Sie natürlich eine separate Liste verwenden. Wenn es für eine Liste nicht gut ist, ist es **, wenn es schwierig ist, die gesamte Liste im Speicher zu halten, und es unnötig ist **. Betrachten Sie beispielsweise dasselbe Beispiel für die Berechnung des N-ten Elements der Fibonacci-Sequenz mithilfe einer Liste:
fibonacci_list.py
f = [0, 1]
n = 2
while n < N:
f.append(f[n-1] + f[n-2])
n += 1
Wenn Sie nur den N-ten Wert erhalten möchten, sind die Werte in der Fibonacci-Sequenz vor dem N-3 eine Speicherverschwendung. Mit anderen Worten, die Szene, in der der Generator arbeitet, ist ** die Szene, in der Sie Werte nacheinander zurückgeben und einen Status haben müssen, aber nicht die gesamte Liste behalten müssen **. Ich möchte, dass Sie sich einen Automaten mit endlichen Zuständen und einem Ausgabesymbol vorstellen. Im Gegenteil, es ist nicht für Situationen geeignet, in denen Sie keinen separaten Status benötigen oder eine Liste führen müssen. Das erste Beispiel dient zum Berechnen einer Hash-Funktion für eine eingegebene Zahl, und das zweite Beispiel dient zum Erstellen einer Liste von Primzahlen. Im Folgenden sehen wir uns einige weitere praktische Beispiele an.
Eine Szene, in der der Generator tatsächlich verwendet wird, ist die Standardbibliothek für die Python-Phrasenanalyse:
http://docs.python.jp/2/library/tokenize.html
Die Phrasenanalyse ist ein Prozess, der häufig beim Kompilieren eines Programms ausgeführt wird. Sie betrachtet das Programm zeichenweise und unterteilt die Zeichenkette des Programms in wichtige Teile (sogenannte Token). Zum Beispiel
def f(hoge, foo):
Ich meine,
def, f, (, hoge, foo, ), :
Es wird wahrscheinlich in eine Token-Zeichenfolge mit dem Namen aufgeteilt.
Sie können an der Zeichenfolge def erkennen, dass es sich um eine Funktionsdefinition handelt, f der Funktionsname ist und die durch Kommas getrennten Variablen zwischen "(" und ")", nachdem es sich um die Argumente handelt. Durch das Parsen von Phrasen werden diese Informationen auch zum Token hinzugefügt und die Token-Zeichenfolge an den nächsten Prozess übergeben (was möglicherweise etwas ungenau ist, weitere Informationen finden Sie in der Compiler-Dokumentation).
Was hier wichtig ist, ist, dass sich die Verarbeitung, wenn Sie das Zeichen ")" sehen, ändert, je nachdem, ob Sie "(" oder nicht) sehen, wenn Sie beispielsweise jedes Zeichen betrachten und analysieren. .. Das heißt, es muss einen ** Zustand ** haben. Sobald diese Funktion definiert ist, ** spielen die Informationen zu dieser Funktion keine Rolle **. Es wäre eine Verschwendung von Speicher, die Informationen des gesamten Programms einzeln zu speichern. Daher ist die Phrasenanalyse eine gute Szene für Generatoren, um eine aktive Rolle zu spielen.
Details werden auf der folgenden Seite erklärt. https://brett.is/writing/about/generator-pipelines-in-python/
Einfach ausgedrückt ist eine Generator-Pipeline ein Prozess, der mehrere Generatoren ** verbindet **. Ich hoffe, Sie können sich die Verarbeitung des Förderbandtyps von der Seite im Werk vorstellen. Das dargestellte Beispiel ist eine Pipeline, die die geraden Elemente einer bestimmten Liste von Ganzzahlen verdreifacht, sie in eine Zeichenfolge konvertiert und sie zurückgibt.
pipeline.py
def even_filter(nums):
for num in nums:
if num % 2 == 0:
yield num
def multiply_by_three(nums):
for num in nums:
yield num * 3
def convert_to_string(nums):
for num in nums:
yield 'The Number: %s' % num
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pipeline = convert_to_string(multiply_by_three(even_filter(nums)))
for num in pipeline:
print num
In diesem Beispiel haben die einzelnen Generatoren keinen Status, verwenden die Generatoren jedoch weiterhin effektiv. Es ist nicht notwendig, die gesamte Liste zu speichern, aber es kann gesagt werden, dass es notwendig ist, sie nacheinander zu verarbeiten. Sie mögen denken, dass die Funktionsverarbeitung in Ordnung ist, aber die Verarbeitung von ungeraden Werten ist im obigen Beispiel nur eine verschwenderische Berechnung. Aus diesem Grund ist ein Generator praktisch, der die Verarbeitung auf jedes Element einzeln anwenden kann.
Die folgenden Websites enthalten detaillierte Erklärungen und Beispiele: http://www.unixuser.org/~euske/doc/python/recursion-j.html
Wie ich zuvor erklärt habe, besteht der große Vorteil von Generatoren darin, dass sie nacheinander verarbeitet werden können, ohne die gesamte Liste zu halten. Mit anderen Worten, es ist kompatibel mit Problemen, die durch iterative Verarbeitung unterteilt und gelöst werden können. Sie können sehen, dass dies sehr nahe an der Idee liegt, Probleme mit Wiederholungen zu lösen. Das Fibonacci-Sequenzbeispiel war ein Beispiel für das Ersetzen der Wiederholung durch einen Generator, aber Sie können den Generator natürlich auch rekursiv verwenden. Der Vorteil der Verwendung eines Generators anstelle einer Funktion besteht darin, dass Sie einen einmal generierten Wert sofort verwerfen können. Der Code, der den Generator rekursiv zum Durchlaufen der Baumstruktur verwendet, sieht beispielsweise folgendermaßen aus:
tree.py
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
def traverse(node):
if node is not None:
for x in traverse(node.left):
yield x
yield t.dat
for x in traverse(node.right):
yield x
Übrigens, wenn Sie die neu aus der python3.3-Reihe hinzugefügte Anweisung "yield from" verwenden, ist die Funktion "traverse" noch sauberer.
def traverse(node):
if node is not None:
yield from traverse(node.left):
yield t.dat
yield from traverse(node.right)
Es wird sein.
Die folgenden Quellen enthalten detaillierte Anweisungen: http://masnun.com/2015/11/13/python-generators-coroutines-native-coroutines-and-async-await.html
Ein Collout ist eine Unterroutine, mit der Sie die Verarbeitung unterbrechen und die Verarbeitung in der Mitte fortsetzen können. In Corroutine können Sie den Wert nicht nur abrufen, sondern auch senden. Nehmen Sie zum Beispiel das folgende Beispiel:
coroutine.py
def coro():
hello = yield "Hello"
yield hello
c = coro()
print(next(c))
print(c.send("World"))
Hier sehen Sie, dass sich die Ausbeute auf der rechten Seite des Zuweisungsausdrucks befindet. Dann sendet die send-Methode die Zeichenfolge "World". Sie können sehen, dass die Eigenschaften von Generatoren mit Zuständen verwendet werden. Wenn Sie sich mit Corroutine befassen, wird dies ein weiterer Artikel sein, daher werde ich ihn hier nur vorstellen. Wenn Sie Lust dazu haben, können Sie auch einen Artikel über Corroutine veröffentlichen. Da natives Kolloutum aus Python 3.5 implementiert wurde, kann dieselbe Verarbeitung ohne Verwendung eines Generators realisiert werden.
Der Generator ist ein schwer zu fassendes Konzept, aber ich denke, es ist überraschend einfach, ihn als ein Werkzeug zu betrachten, das einen Status hat und Werte in der Reihenfolge mit nur teilweiser Behandlung zurückgibt, ohne die gesamte Liste zu halten. Lasst uns alle den Generator benutzen und coole Programme schreiben! (Obwohl ich es noch nicht gemeistert habe).
Recommended Posts