Python-Ausbeute, Generatoren und Generatorausdrücke

In diesem Tutorial erfahren Sie, wie Sie mit Python-Generatoren einfach Iterationen erstellen, wie sie sich von Iteratoren und normalen Funktionen unterscheiden und warum Sie sie verwenden sollten.

Video: Python-Generatoren

Generatoren in Python

Das Erstellen eines Iterators in Python erfordert viel Arbeit. Wir müssen eine Klasse mit __iter__()und __next__()method implementieren , interne Zustände verfolgen und erhöhen, StopIterationwenn keine Werte zurückgegeben werden müssen.

Dies ist sowohl langwierig als auch nicht intuitiv. Generator kommt in solchen Situationen zur Rettung.

Python-Generatoren sind eine einfache Möglichkeit, Iteratoren zu erstellen. Alle oben genannten Arbeiten werden automatisch von Generatoren in Python ausgeführt.

Einfach ausgedrückt ist ein Generator eine Funktion, die ein Objekt (Iterator) zurückgibt, über das wir iterieren können (jeweils einen 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, jedoch mit einer yieldAnweisung anstelle einer returnAnweisung.

Wenn eine Funktion mindestens eine yieldAnweisung enthält (sie kann andere yieldoder returnAnweisungen enthalten), wird sie zu einer Generatorfunktion. Beide yieldund returngeben einen Wert von einer Funktion zurück.

Der Unterschied besteht darin, dass während eine returnAnweisung eine Funktion vollständig beendet, die yieldAnweisung die Funktion pausiert, alle ihre Zustände speichert und später bei aufeinanderfolgenden Aufrufen fortfährt.

Unterschiede zwischen Generatorfunktion und Normalfunktion

So unterscheidet sich eine Generatorfunktion von einer normalen Funktion.

  • Die Generatorfunktion enthält eine oder mehrere yieldAnweisungen.
  • Beim Aufruf wird ein Objekt (Iterator) zurückgegeben, die Ausführung wird jedoch nicht sofort gestartet.
  • Methoden wie __iter__()und __next__()werden automatisch implementiert. So können wir die Elemente mit durchlaufen next().
  • Sobald die Funktion nachgibt, wird die Funktion angehalten und die Steuerung an den Anrufer übertragen.
  • Lokale Variablen und ihre Zustände werden zwischen aufeinanderfolgenden Aufrufen gespeichert.
  • Wenn die Funktion beendet wird, StopIterationwird sie bei weiteren Aufrufen automatisch ausgelöst.

Hier ist ein Beispiel, um alle oben genannten Punkte zu veranschaulichen. Wir haben eine Generatorfunktion my_gen()mit mehreren yieldAnweisungen.

 # 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

Ein interaktiver Lauf 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 zwischen jedem Aufruf gespeichert wird.

Im Gegensatz zu normalen Funktionen werden die lokalen Variablen nicht zerstört, wenn die Funktion nachgibt. Darüber hinaus kann das Generatorobjekt nur einmal iteriert werden.

Um den Prozess neu zu starten, müssen wir ein anderes Generatorobjekt mit so etwas wie erstellen a = my_gen().

Eine letzte Sache zu beachten ist, dass wir Generatoren mit for-Schleifen direkt verwenden können.

Dies liegt daran, dass eine forSchleife einen Iterator verwendet und ihn mithilfe der next()Funktion durchläuft . Es endet automatisch, wenn StopIterationes angehoben wird. Überprüfen Sie hier, wie eine for-Schleife tatsächlich in Python implementiert ist.

 # 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 wie folgt:

 Dies wird zuerst gedruckt 1 Dies wird als zweites gedruckt 2 Dies wird zuletzt gedruckt 3

Python-Generatoren mit einer Schleife

Das obige Beispiel ist weniger nützlich und wir haben es untersucht, um eine Vorstellung davon zu bekommen, was im Hintergrund geschah.

Normalerweise werden Generatorfunktionen mit einer Schleife implementiert, die eine geeignete Abschlussbedingung aufweist.

Nehmen wir ein Beispiel eines Generators, 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

 olleh

In diesem Beispiel haben wir die range()Funktion verwendet, um den Index mithilfe der for-Schleife in umgekehrter Reihenfolge abzurufen.

Hinweis : Diese Generatorfunktion funktioniert nicht nur mit Zeichenfolgen, sondern auch mit anderen Arten von Iterablen wie Liste, Tupel usw.

Python-Generator-Ausdruck

Einfache Generatoren können mithilfe von Generatorausdrücken einfach im laufenden Betrieb erstellt werden. Es macht den Bau 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. Die eckigen Klammern werden jedoch 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 jeweils ein Element erzeugt.

They have lazy execution ( producing items only when asked for ). For this reason, a generator expression is much more memory efficient than an equivalent list comprehension.

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

Output

 (1, 9, 36, 100) 

We can see above that the generator expression did not produce the required result immediately. Instead, it returned a generator object, which produces items only on demand.

Here is how we can start getting items from the generator:

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

When we run the above program, we get the following output:

 1 9 36 100 Traceback (most recent call last): File "", line 15, in StopIteration

Generator expressions can be used as function arguments. When used in such a way, the round parentheses can be dropped.

 >>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100

Use of Python Generators

There are several reasons that make generators a powerful implementation.

1. Easy to Implement

Generators can be implemented in a clear and concise way as compared to their iterator class counterpart. Following is an example to implement a sequence of power of 2 using an iterator class.

 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

The above program was lengthy and confusing. Now, let's do the same using a generator function.

 def PowTwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 1

Since generators keep track of details automatically, the implementation was concise and much cleaner.

2. Memory Efficient

A normal function to return a sequence will create the entire sequence in memory before returning the result. This is an overkill, if the number of items in the sequence is very large.

Generator implementation of such sequences is memory friendly and is preferred since it only produces one item at a time.

3. Represent Infinite Stream

Generatoren sind ausgezeichnete Medien, um einen unendlichen Datenstrom darzustellen. Unendliche Streams können nicht im Speicher gespeichert werden. Da Generatoren jeweils nur ein Element produzieren, 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. Pipelining-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 wollen, können wir dies auf folgende Weise tun, indem wir die Ausgabe von Generatorfunktionen zusammenpipeln.

 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 leicht zu lesen (und ja, viel cooler!).

Interessante Beiträge...