Hinter jeder for-Schleife steckt ein Iterator: ein Objekt, das Element für Element herausgibt. Mit Generatoren schreibst du solche Objekte besonders einfach, und zwar mit dem Schlüsselwort yield.
Wie for wirklich funktioniert
Listen, Texte, Dictionaries und andere Sammlungen sind iterierbar. Mit iter() bekommst du einen Iterator, mit next() das jeweils nächste Element. Ist nichts mehr da, entsteht StopIteration:
it = iter(["a", "b", "c"])
print(next(it))
print(next(it))
print(next(it))
print(next(it, "Ende")) # mit Standardwert statt Fehlera b c Ende
Eine Funktion mit yield
Eine Funktion mit yield heißt Generatorfunktion. Beim Aufruf läuft sie nicht sofort los, sondern liefert einen Generator. Bei jedem next() läuft sie bis zum nächsten yield und pausiert dort:
def zaehle_bis(n):
i = 1
while i <= n:
yield i
i += 1
for zahl in zaehle_bis(4):
print(zahl)
gen = zaehle_bis(2)
print(next(gen), next(gen))1 2 3 4 1 2
Der Vorteil: Die Werte entstehen erst, wenn sie gebraucht werden. Ein Generator braucht fast keinen Speicher, auch wenn er Millionen Werte liefert.
Beispiel: Fibonacci-Zahlen
def fibonacci():
a, b = 0, 1
while True: # unendlich! – aber nur bei Bedarf berechnet
yield a
a, b = b, a + b
from itertools import islice
print(list(islice(fibonacci(), 10)))[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
itertools.islice nimmt die ersten Elemente eines (auch unendlichen) Iterators.
Generator-Ausdrücke
Mit runden Klammern statt eckigen entsteht ein Generator ohne eigene Funktion:
quadrate = (x * x for x in range(1, 6))
print(next(quadrate))
print(sum(quadrate)) # summiert die restlichen vier1 54
Ein Generator kann man nur einmal durchlaufen:
g = (x for x in [1, 2, 3])
print(list(g))
print(list(g))[1, 2, 3] []
Eigene Iteratoren
Eine Klasse wird zum Iterator, wenn sie __iter__ und __next__ besitzt. In den meisten Fällen ist ein Generator aber die einfachere Lösung:
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1
print(list(Countdown(3)))[3, 2, 1]
Nützliche Werkzeuge aus itertools
| Funktion | Wirkung |
|---|---|
count(start) | unendlich zählen |
cycle(seq) | Folge endlos wiederholen |
islice(it, n) | die ersten n Elemente |
chain(a, b) | mehrere Folgen hintereinander |
product(a, b) | alle Kombinationen (kartesisches Produkt) |
permutations(seq) | alle Anordnungen |
combinations(seq, k) | alle Auswahlen ohne Reihenfolge |
from itertools import chain, combinations, product
print(list(chain([1, 2], "ab")))
print(list(combinations("ABC", 2)))
print(list(product([0, 1], repeat=2)))[1, 2, 'a', 'b']
[('A', 'B'), ('A', 'C'), ('B', 'C')]
[(0, 0), (0, 1), (1, 0), (1, 1)]Merke
- Ein Iterator liefert mit
next()Element für Element;StopIterationbedeutet "fertig" yieldmacht aus einer Funktion einen Generator, der zwischen den Werten pausiert- Generatoren sparen Speicher und können unendlich sein
- Generator-Ausdruck:
(ausdruck for x in folge) - Ein Generator lässt sich nur einmal durchlaufen
itertoolsbringt viele fertige Bausteine
Aufgabe
Schreibe einen Generator gerade_zahlen(grenze), der alle geraden Zahlen bis zur Grenze liefert, und gib seine Werte mit einer Schleife aus.