Ein generator ist eine Funktion oder ein Ausdruck, der einen speziellen Typ von Iterator zurückgibt, den sogenannten generator iterator.
Generator-iterators sind lazy: Sie speichern ihre values nicht im Speicher, sondern erzeugen ihre Werte erst, wenn sie gebraucht werden.
Eine Generatorfunktion sieht aus wie jede andere Funktion, enthält aber einen oder mehrere yield-Ausdrücke.
Jedes yield unterbricht die Ausführung des Codes und speichert den aktuellen Ausführungszustand (einschließlich aller lokalen Variablen und try-Anweisungen).
Wenn der Generator fortgesetzt wird, nimmt er den Zustand von der Unterbrechung wieder auf, anders als reguläre Funktionen, die mit jedem Aufruf zurückgesetzt werden.
Generatoren werden ähnlich konstruiert wie andere Schleifen- oder rekursive Funktionen, benötigen aber einen yield-Ausdruck, den wir uns später noch genauer ansehen.
Ein Beispiel ist eine Funktion, die die Quadrate aus einer gegebenen Liste von Zahlen zurückgibt. So wie sie derzeit geschrieben ist, muss die gesamte Eingabe verarbeitet werden, bevor Werte zurückgegeben werden können:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
Du kannst diese Funktion so in einen Generator umwandeln:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
Der Grundgedanke dahinter ist, dass du einen Generator verwendest, wenn du nicht alle Werte auf einmal erzeugen musst. Das spart Speicher und Rechenleistung, da nur der Wert berechnet wird, an dem du gerade arbeitest.
Generatoren können in Python die meisten iterables ersetzen.
Das schließt Funktionen oder Objekte ein, die ein iterable/iterator als Argument erwarten.
So verwendest du den Generator squares_generator():
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
Werte innerhalb eines generator können auch über die Funktion next() erzeugt oder abgerufen werden.
next() ruft die Methode __next__() eines Generator-Iterator-Objekts auf, „bringt“ den Code also voran und wertet ihn bis zu seinem yield-Ausdruck aus, der dann einen Wert „liefert“ oder zurückgibt:
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
Wenn ein generator-iterator vollständig verbraucht ist und keine Werte mehr zurückzugeben hat, wirft er einen StopIteration-Fehler.
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Generator-Iteratoren sind eine spezielle Teilmenge der Iteratoren.
Iterators sind der Mechanismus bzw. das Protokoll, das das Durchlaufen von iterables ermöglicht.
Generator-Iteratoren und die Iteratoren, die von gängigen Python-iterables zurückgegeben werden, verhalten sich sehr ähnlich, aber es gibt einige wichtige Unterschiede:
indexes.
Du kannst nicht per Addition oder Subtraktion auf einen vorherigen oder zukünftigen Wert verweisen und kannst weder die eckige Klammer-Notation ([]) noch Slicing verwenden.len() verwendet werden, da sie keine Länge haben.generator-iterator sammelst!Der yield-Ausdruck ist dem return-Ausdruck sehr ähnlich.
Anders als beim return-Ausdruck gibt yield Werte an den Aufrufer an einem bestimmten Punkt ab und unterbricht die Auswertung/Rückgabe weiterer Werte, bis diese angefordert werden.
Wenn yield ausgewertet wird, pausiert es die Ausführung der umgebenden Funktion und gibt alle Werte der Funktion zu diesem Zeitpunkt zurück.
Die Funktion bleibt dann im Gültigkeitsbereich, und wenn __next__() aufgerufen wird, wird die Ausführung fortgesetzt, bis yield erneut angetroffen wird.
Die Verwendung von yield-Ausdrücken ist außerhalb von Funktionen nicht erlaubt.
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
Generatoren sind in vielen Anwendungen nützlich.
Wenn du mit einer potenziell großen Sammlung von Werten arbeitest, möchtest du vielleicht nicht alle davon in den Speicher laden. Ein Generator kann verwendet werden, um größere Daten Stück für Stück zu verarbeiten, was Speicher spart und die Leistung verbessert.
Generatoren sind auch sehr hilfreich, wenn ein Prozess oder eine Berechnung komplex, teuer oder unendlich ist:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
Wenn nun __next__() für das Objekt infinite_sequence aufgerufen wird, gibt es die vorherige Zahl + 1 zurück.
Conda Airlines ist die größte Fluggesellschaft der Programmierwelt und hat über 10.000 Flüge pro Tag!
Momentan verteilen sie alle Sitze noch von Hand an die Passagiere; das muss automatisiert werden.
Sie haben dich gebeten, eine Software zu entwickeln, die die Sitzplatzvergabe automatisch übernimmt. Deine Software soll speichereffizient und leistungsfähig sein.
Conda möchte Sitzbuchstaben für ihre Flugzeuge erzeugen.
Ein Flugzeug besteht aus Sitzreihen.
Jede Reihe hat 4 Sitze.
Die Sitze in jeder Reihe heißen immer A, B, C und D.
Der erste Sitz in der Reihe ist A, der zweite Sitz in der Reihe ist B und so weiter.
Nach D beginnt es wieder mit A.
Implementiere eine Funktion generate_seat_letters(<number>), die ein int entgegennimmt, das angibt, wie viele Sitzbuchstaben erzeugt werden sollen.
Die Funktion soll dann ein Iterable von Sitzbuchstaben zurückgeben.
>>> letters = generate_seat_letters(4)
>>> next(letters)
"A"
>>> next(letters)
"B"
Conda möchte ein System, das eine bestimmte Anzahl von Sitzen für ihre Flugzeuge erzeugen kann.
Jedes Flugzeug hat 4 Sitze pro Reihe.
Die Reihen werden mit Zahlen nummeriert, beginnend bei 1 und aufsteigend.
Die Sitze sollen in dieser Reihenfolge angeordnet sein: 1A, 1B, 1C, 1D, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, ...
Hier ein Beispiel:
| x | 1 | 2 |
|---|---|---|
| Reihe | 5 | 21 |
| Sitzbuchstabe | A | D |
| Ergebnis | 5A | 21D |
Viele Fluggesellschaften haben wegen des Aberglaubens ihrer Passagiere keine Reihe 13 auf ihren Flügen. Conda Airlines hält sich ebenfalls an diese Konvention, also stelle sicher, dass du für Reihe 13 keine Sitze erzeugst.
Implementiere eine Funktion generate_seats(<number>), die ein int entgegennimmt, das angibt, wie viele Sitze erzeugt werden sollen.
Die Funktion soll dann ein Iterable der angegebenen Sitze zurückgeben.
>>> seats = generate_seats(10)
>>> next(seats)
"1A"
>>> next(seats)
"1B"
Jetzt, wo du eine Funktion hast, die Sitze erzeugt, kannst du damit Sitze an Passagiere vergeben.
Implementiere eine Funktion assign_seats(<passengers>), die eine list mit Passagiernamen entgegennimmt.
Die Funktion soll dann ein Wörterbuch zurückgeben, in dem passenger der Schlüssel und seat_number der Wert ist.
>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob']
>>> assign_seats(passengers)
{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}
Conda Airlines möchte für jedes Ticket einen eindeutigen Code haben. Da sie eine große Fluggesellschaft sind, haben sie viele Flüge. Das bedeutet, dass es mehrere Flüge mit derselben Sitznummer gibt. Sie möchten, dass du ein System entwickelst, das ein eindeutiges Ticket als 12 Zeichen langen String erzeugt.
Dieser Code beginnt mit dem assigned_seat, gefolgt von der flight_id.
Der Rest des Codes wird mit 0s aufgefüllt.
Implementiere eine Funktion generate_codes(<seat_numbers>, <flight_id>), die eine list mit seat_numbers und einen string mit der Flugnummer entgegennimmt.
Die Funktion soll dann einen generator zurückgeben, der eine ticket_number liefert.
>>> seat_numbers = ['1A', '17D']
>>> flight_id = 'CO1234'
>>> ticket_ids = generate_codes(seat_numbers, flight_id)
>>> next(ticket_ids)
'1ACO12340000'
>>> next(ticket_ids)
'17DCO1234000'
Melde dich bei Exercism an, um Python mit 17 Konzepte146 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.