Python-Generatoren
Python-Generatoren
In diesem Tutorial erfahren Sie, wie Sie Iterationen einfach mit Python-Generatoren erstellen, wie sie sich von Iteratoren und normalen Funktionen unterscheiden und warum Sie sie verwenden sollten.
Video:Python-Generatoren
Generatoren in Python
Es gibt viel Arbeit beim Erstellen eines Iterators in Python. Wir müssen eine Klasse mit __iter__()
implementieren und __next__()
-Methode, interne Zustände verfolgen und StopIteration
auslösen wenn es keine zurückzugebenden Werte gibt.
Dies ist sowohl langwierig als auch kontraintuitiv. Generator kommt in solchen Situationen zur Hilfe.
Python-Generatoren sind eine einfache Möglichkeit, Iteratoren zu erstellen. Alle oben erwähnten Arbeiten werden automatisch von Generatoren in Python erledigt.
Einfach gesagt ist ein Generator eine Funktion, die ein Objekt (Iterator) zurückgibt, über das wir iterieren können (jeweils ein Wert).
Generatoren in Python erstellen
Es ist ziemlich einfach, einen Generator in Python zu erstellen. Es ist so einfach wie das Definieren einer normalen Funktion, aber mit einem yield
-Anweisung anstelle eines return
Aussage.
Wenn eine Funktion mindestens einen yield
enthält -Anweisung (kann andere yield
enthalten oder return
Anweisungen), wird es zu einer Generatorfunktion. Beide yield
und return
gibt einen Wert von einer Funktion zurück.
Der Unterschied besteht darin, dass während ein return
Anweisung beendet eine Funktion vollständig, yield
-Anweisung hält die Funktion an, speichert alle ihre Zustände und fährt später von dort aus bei aufeinanderfolgenden Aufrufen fort.
Unterschiede zwischen Generatorfunktion und Normalfunktion
So unterscheidet sich eine Generatorfunktion von einer normalen Funktion.
- Generatorfunktion enthält einen oder mehrere
yield
Aussagen. - Wenn es aufgerufen wird, gibt es ein Objekt (Iterator) zurück, beginnt aber nicht sofort mit der Ausführung.
- Methoden wie
__iter__()
und__next__()
werden automatisch implementiert. So können wir mitnext()
durch die Elemente iterieren . - Sobald die Funktion nachgibt, wird die Funktion angehalten und die Steuerung an den Aufrufer übertragen.
- Lokale Variablen und ihre Zustände werden zwischen aufeinanderfolgenden Aufrufen gespeichert.
- Schließlich, wenn die Funktion beendet wird,
StopIteration
wird bei weiteren Anrufen automatisch ausgelöst.
Hier ist ein Beispiel, um alle oben genannten Punkte zu veranschaulichen. Wir haben eine Generatorfunktion namens my_gen()
mit mehreren yield
Aussagen.
# A simple generator function
def my_gen():
n = 1
print('This is printed first')
# Generator function contains yield statements
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
Eine interaktive Ausführung im Interpreter ist unten angegeben. Führen Sie diese in der Python-Shell aus, um die Ausgabe anzuzeigen.
>>> # It returns an object but does not start execution immediately.
>>> a = my_gen()
>>> # We can iterate through the items using next().
>>> next(a)
This is printed first
1
>>> # Once the function yields, the function is paused and the control is transferred to the caller.
>>> # Local variables and theirs states are remembered between successive calls.
>>> next(a)
This is printed second
2
>>> next(a)
This is printed at last
3
>>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration
Eine interessante Sache, die im obigen Beispiel zu beachten ist, ist, dass der Wert der Variablen n wird zwischen jedem Aufruf gespeichert.
Im Gegensatz zu normalen Funktionen werden die lokalen Variablen nicht zerstört, wenn die Funktion nachgibt. Außerdem kann das Generator-Objekt nur einmal iteriert werden.
Um den Prozess neu zu starten, müssen wir ein weiteres Generatorobjekt mit etwas wie a = my_gen()
erstellen .
Eine letzte Anmerkung ist, dass wir Generatoren direkt mit for-Schleifen verwenden können.
Dies liegt daran, dass ein for
Schleife nimmt einen Iterator und iteriert darüber mit next()
Funktion. Es endet automatisch bei StopIteration
wird angehoben. Sehen Sie hier nach, wie eine for-Schleife tatsächlich in Python implementiert wird.
# A simple generator function
def my_gen():
n = 1
print('This is printed first')
# Generator function contains yield statements
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
# Using for loop
for item in my_gen():
print(item)
Wenn Sie das Programm ausführen, lautet die Ausgabe:
This is printed first 1 This is printed second 2 This is printed at last 3
Python-Generatoren mit einer Schleife
Das obige Beispiel ist weniger nützlich und wir haben es nur untersucht, um eine Vorstellung davon zu bekommen, was im Hintergrund passiert.
Üblicherweise werden Generatorfunktionen mit einer Schleife mit geeigneter Abbruchbedingung realisiert.
Nehmen wir ein Beispiel für einen Generator, der eine Zeichenfolge umkehrt.
def rev_str(my_str):
length = len(my_str)
for i in range(length - 1, -1, -1):
yield my_str[i]
# For loop to reverse the string
for char in rev_str("hello"):
print(char)
Ausgabe
o l l e h
In diesem Beispiel haben wir den range()
verwendet Funktion, um den Index mithilfe der for-Schleife in umgekehrter Reihenfolge abzurufen.
Hinweis :Diese Generatorfunktion funktioniert nicht nur mit Strings, sondern auch mit anderen Arten von Iterablen wie Liste, Tupel usw.
Python-Generatorausdruck
Einfache Generatoren lassen sich mithilfe von Generatorausdrücken ganz einfach on-the-fly erstellen. Es macht das Bauen von Generatoren einfach.
Ähnlich wie die Lambda-Funktionen, die anonyme Funktionen erstellen, erstellen Generatorausdrücke anonyme Generatorfunktionen.
Die Syntax für den Generatorausdruck ähnelt der eines Listenverständnisses in Python. Aber die eckigen Klammern werden durch runde Klammern ersetzt.
Der Hauptunterschied zwischen einem Listenverständnis und einem Generatorausdruck besteht darin, dass ein Listenverständnis die gesamte Liste erzeugt, während der Generatorausdruck ein Element nach dem anderen erzeugt.
Sie haben eine faule Ausführung (Produzieren von Gegenständen nur, wenn danach gefragt wird). Aus diesem Grund ist ein Generatorausdruck viel speichereffizienter als ein entsprechendes Listenverständnis.
# Initialize the list
my_list = [1, 3, 6, 10]
# square each term using list comprehension
list_ = [x**2 for x in my_list]
# same thing can be done using a generator expression
# generator expressions are surrounded by parenthesis ()
generator = (x**2 for x in my_list)
print(list_)
print(generator)
Ausgabe
[1, 9, 36, 100] <generator object <genexpr> at 0x7f5d4eb4bf50>
Wir können oben sehen, dass der Generatorausdruck nicht sofort das gewünschte Ergebnis lieferte. Stattdessen wurde ein Generatorobjekt zurückgegeben, das Artikel nur bei Bedarf produziert.
So können wir anfangen, Items vom Generator zu bekommen:
# Initialize the list
my_list = [1, 3, 6, 10]
a = (x**2 for x in my_list)
print(next(a))
print(next(a))
print(next(a))
print(next(a))
next(a)
Wenn wir das obige Programm ausführen, erhalten wir die folgende Ausgabe:
1 9 36 100 Traceback (most recent call last): File "<string>", line 15, in <module> StopIteration
Generatorausdrücke können als Funktionsargumente verwendet werden. Bei einer solchen Verwendung können die runden Klammern weggelassen werden.
>>> sum(x**2 for x in my_list)
146
>>> max(x**2 for x in my_list)
100
Verwendung von Python-Generatoren
Es gibt mehrere Gründe, die Generatoren zu einer leistungsstarken Implementierung machen.
1. Einfach zu implementieren
Generatoren können im Vergleich zu ihrem Gegenstück in der Iteratorklasse klar und prägnant implementiert werden. Im Folgenden finden Sie ein Beispiel zur Implementierung einer Potenzfolge von 2 mithilfe einer Iteratorklasse.
class PowTwo:
def __init__(self, max=0):
self.n = 0
self.max = max
def __iter__(self):
return self
def __next__(self):
if self.n > self.max:
raise StopIteration
result = 2 ** self.n
self.n += 1
return result
Das obige Programm war langwierig und verwirrend. Lassen Sie uns nun dasselbe mit einer Generatorfunktion tun.
def PowTwoGen(max=0):
n = 0
while n < max:
yield 2 ** n
n += 1
Da Generatoren Details automatisch verfolgen, war die Implementierung präzise und viel sauberer.
2. Speichereffizient
Eine normale Funktion zum Zurückgeben einer Sequenz erstellt die gesamte Sequenz im Speicher, bevor das Ergebnis zurückgegeben wird. Dies ist ein Overkill, wenn die Anzahl der Elemente in der Sequenz sehr groß ist.
Die Generatorimplementierung solcher Sequenzen ist speicherfreundlich und wird bevorzugt, da sie jeweils nur ein Element erzeugt.
3. Stellt den unendlichen Strom dar
Generatoren sind hervorragende Medien, um einen unendlichen Datenstrom darzustellen. Unendliche Ströme können nicht im Speicher gespeichert werden, und da Generatoren jeweils nur ein Element erzeugen, können sie einen unendlichen Datenstrom darstellen.
Die folgende Generatorfunktion kann (zumindest theoretisch) alle geraden Zahlen erzeugen.
def all_even():
n = 0
while True:
yield n
n += 2
4. Pipeline-Generatoren
Mehrere Generatoren können verwendet werden, um eine Reihe von Operationen zu leiten. Dies lässt sich am besten anhand eines Beispiels veranschaulichen.
Angenommen, wir haben einen Generator, der die Zahlen in der Fibonacci-Reihe erzeugt. Und wir haben einen weiteren Generator zum Quadrieren von Zahlen.
Wenn wir die Summe der Quadrate von Zahlen in der Fibonacci-Reihe herausfinden möchten, können wir dies auf folgende Weise tun, indem wir die Ausgabe von Generatorfunktionen zusammenfügen.
def fibonacci_numbers(nums):
x, y = 0, 1
for _ in range(nums):
x, y = y, x+y
yield x
def square(nums):
for num in nums:
yield num**2
print(sum(square(fibonacci_numbers(10))))
Ausgabe
4895
Dieses Pipelining ist effizient und einfach zu lesen (und ja, viel cooler!).
Python