Generatoren in Python

Generatoren in Python

Dieser Artikel erklärt Generatoren in Python.

YouTube Video

Generatoren in Python

Überblick

Python-Generatoren sind eine Art Iterator und eine leistungsstarke Funktion, um wiederholte Verarbeitung effizient durchzuführen. Sie ermöglichen das Schreiben von speichereffizientem Code, wenn große Datenmengen verarbeitet werden.

Was ist ein Generator?

Ein Generator in Python ist eine spezielle Funktion, die jeweils einen Wert erzeugt, definiert durch das Schlüsselwort yield. Sein Merkmal ist, dass er die Ausführung unter Beibehaltung seines Zustands pausiert und später fortsetzen kann.

Grundlagen von yield

yield ist ein Schlüsselwort, das einen Wert zurückgibt und gleichzeitig die Funktionsausführung pausiert.

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8print(next(gen))  # 1
 9print(next(gen))  # 2
10print(next(gen))  # 3
  • Beim Aufrufen gibt diese Funktion ein Generatorobjekt zurück, das Werte nacheinander liefert.
  • Wenn Sie next() aufrufen, obwohl kein nächster Wert mehr vorhanden ist, tritt ein StopIteration-Fehler auf.

next() und StopIteration

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8try:
 9    while True:
10        value = next(gen)
11        print(value)
12except StopIteration:
13    print("Finished")
  • Indem Sie den StopIteration-Fehler explizit auf diese Weise behandeln, können Sie erkennen, wann ein Generator abgeschlossen ist.

send(value)

send(value) aufzurufen setzt den Generator fort und übergibt value an die Stelle des yield-Ausdrucks. Der gesendete Wert kann auf der Generatorseite als Rückgabewert des yield-Ausdrucks empfangen werden. Beim ersten Aufruf kann mit send(value) nur None gesendet werden, deshalb muss next() oder send(None) verwendet werden.

 1def gen():
 2    x = yield 1
 3    print(f"x = {x}")
 4    y = yield 2
 5    print(f"y = {y}")
 6
 7g = gen()
 8print(next(g))       # -> 1 (value from yield 1)
 9print(g.send(10))    # -> x = 10, 2 (value from yield 2)
10print(g.send(20))    # -> y = 20, StopIteration occurs
  • Mit send(10) wird das yield des Generators zu einem Ausdruck, der 10 zurückgibt, und 10 wird x zugewiesen.

throw()

throw aufzurufen setzt den Generator fort und löst eine Ausnahme an der Stelle des pausierten yield aus. Die Ausnahme kann innerhalb des Generators behandelt werden, um die Verarbeitung fortzusetzen. Wenn die Ausnahme nicht abgefangen wird, wird sie nach außen weitergegeben und der Generator endet.

 1def gen():
 2    try:
 3        yield 1
 4    except ValueError as e:
 5        print(f"Caught: {e}")
 6        yield "recovered"
 7
 8g = gen()
 9print(next(g))   # -> 1
10print(g.throw(ValueError("boom")))  # -> Caught: boom, "recovered"
  • In diesem Code wird throw aufgerufen, um eine Ausnahme in den Generator einzufügen. Auf der Generatorseite wird die Ausnahme behandelt und recovered wird zurückgegeben.

close()

close() aufzurufen beendet den Generator. Innerhalb des Generators kann die Bereinigung mit finally durchgeführt werden. next() oder send() nach dem Aufruf von close() aufzurufen löst einen StopIteration-Fehler aus.

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • Dieser Code zeigt, dass der Aufruf von close() den Generator beendet und den Bereinigungsprozess in finally auslöst.

yield from

yield from ist eine Syntax, die verwendet wird, um an einen Subgenerator zu delegieren. Es ist eine einfache Möglichkeit, innerhalb eines Generators einen weiteren Generator aufzurufen und alle dessen Werte an den äußeren Gültigkeitsbereich weiterzugeben.

1def sub_gen():
2    yield 1
3    yield 2
4
5def main_gen():
6    yield from sub_gen()
7    yield 3
8
9print(list(main_gen()))  # -> [1, 2, 3]
  • Dieser Code delegiert alle Werte vom Subgenerator mithilfe von yield from an den äußeren Generator und gibt anschließend 3 zurück.

Beziehung zu Iteratoren

Generatoren implementieren intern __iter__() und __next__() und sind somit eine Art von Iterator. Daher sind sie vollständig kompatibel mit iterierbaren Operationen wie for-Schleifen.

Integration mit for-Schleifen

In Python verwendet eine for-Schleife intern next(), um Werte automatisch abzurufen.

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

Mit dieser Methode erfolgt auch die Behandlung von StopIteration automatisch.

Erstellen unendlicher Generatoren

1def count_up(start=0):
2    while True:
3        yield start
4        start += 1
5
6counter = count_up()
7print(next(counter))  # 0
8print(next(counter))  # 1

Es ist möglich, unendliche Schleifen zu erstellen, aber beim Einsatz ist Vorsicht geboten.

Generatorausdrücke

Generatorausdrücke, die in Klammern geschrieben werden, ermöglichen die Definition von Generatoren mit einer Syntax ähnlich zu List Comprehensions.

1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8    print(square)

Im Gegensatz zu List Comprehensions laden sie nicht alle Elemente auf einmal in den Speicher, wodurch sie speichereffizienter sind.

Fehlerbehandlung in Generatoren

Ausnahmen können innerhalb eines Generators auftreten. In solchen Fällen verwendet man try-except wie im normalen Python-Code.

 1def safe_divide_generator(numbers, divisor):
 2    """Yields results of dividing numbers by a given divisor safely."""
 3    for number in numbers:
 4        try:
 5            yield number / divisor  # Attempt to divide and yield result.
 6        except ZeroDivisionError:
 7            yield float('inf')  # Return infinity if division by zero occurs.
 8
 9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0)  # Create generator with divisor as 0.
12for value in gen:
13    print(value)  # Output: inf, inf, inf

In diesem Beispiel erfolgt eine korrekte Fehlerbehandlung im Falle einer Division durch Null.

Stack-Trace eines Generators

Tritt innerhalb des Generators eine Ausnahme auf, wird sie ausgelöst, sobald der Generator fortgesetzt wird.

 1def error_generator():
 2    """A generator that yields values and raises an error."""
 3    yield 1
 4    raise ValueError("An error occurred")  # Raise a ValueError intentionally.
 5    yield 2
 6
 7gen = error_generator()
 8print(next(gen))       # Output: 1 (first value yielded)
 9try:
10    print(next(gen))   # Attempt to get the next value, which raises an error
11except ValueError as e:
12    print(e)           # Output: An error occurred (exception message is printed)
  • Dieser Generator liefert zuerst 1 zurück. Der beim Fortsetzen ausgelöste Fehler wird abgefangen und als Fehlermeldung angezeigt.

Beispiele für die Verwendung von Generatoren

Zeilenweises Lesen einer Datei (geeignet für große Dateien)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • Diese Funktion liest eine Textdatei zeilenweise mit einem Iterator ein, entfernt Leerzeichen von jeder Zeile und gibt diese als Generator zurück, sodass große Dateien mit geringem Speicherverbrauch verarbeitet werden können.

Generator für die Fibonacci-Folge

1def fibonacci(limit):
2    a, b = 0, 1
3    while a < limit:
4        yield a
5        a, b = b, a + b
6
7for n in fibonacci(100):
8    print(n)
  • Dieser Code verwendet einen Generator, um der Reihe nach Fibonacci-Zahlen zu erzeugen, die unter dem angegebenen Grenzwert liegen, und gibt sie mit einer for-Schleife aus.

Anwendungsfälle

Generatoren können auch in den folgenden Szenarien verwendet werden.

  • Sequenzielle Verarbeitung von großen CSV- oder Logdateien
  • API-Paginierung
  • Verarbeitung von Streaming-Daten (z. B. Kafka, IoT-Geräte)

Zusammenfassung

Konzept Kernaussage
yield Pausiert und gibt einen Wert zurück
Generatorfunktion Eine Funktion, die yield enthält und beim Aufruf einen Iterator zurückgibt
Vorteile Speichereffizient und ideal für die Verarbeitung großer Datenmengen
Generatorausdruck Ermöglicht eine knappe Syntax wie (x for x in iterable)

Durch die Verwendung von Generatoren können große Datenmengen effizient verarbeitet werden, während Speicher gespart und der Code übersichtlich gehalten wird.

Sie können den obigen Artikel mit Visual Studio Code auf unserem YouTube-Kanal verfolgen. Bitte schauen Sie sich auch den YouTube-Kanal an.

YouTube Video