Generatoren
Generatoren sind eine spezielle Form von Iteratoren, allerdings laufen diese nicht über eine Liste von bestehenden Werten sondern erzeugen den jeweiligen aktuellen Wert auf Abruf.
Der folgende Code zum Laufen durch eine Liste mit Zahlen kennst du schon.
zahlen = [1,2,3,4,5,6,7,8,9,10]
for n in zahlen:
print(n)
Die Liste muss ständig im Speicher vorgehalten werden, auch dann, wenn sie nicht benötigt wird. Bei 10 Zahlen ist das noch zu verkraften. Aber bei 100 Mio. Einträgen in der Liste mit komplexeren Datenstrukturen wird das schwierig. Als Lösung bieten sich dafür Generatoren an.
def gen_zahl():
n = 0
while n < 10:
n += 1
yield n
zahlen = gen_zahl()
print(next(zahlen))
print(next(zahlen))
print(next(zahlen))
print(next(zahlen))
print(next(zahlen))
print(next(zahlen))
print(next(zahlen))
print(next(zahlen))
print(next(zahlen))
print(next(zahlen))
Die Methode gen_zahl()
stellt einen Generator für die ersten 10 natürlichen Zahlen zur Verfügung. Der Code sieht nicht sehr besonders aus, bis auf das Schlüsselwort yield
yield
yield
ist eine besondere Art des return Statements. Während return
den ermittelten Rückgabewert zurückliefert und die Methode dann beendet, wird bei yield
der Wert zurückgegeben, aber der Zustand der Generator Funktion abgespeichert und beim nächsten Aufruf des Generators an der Stelle (d.h. hier mit der while
-Schleife) fortgefahren.
Dadurch wird bei jedem Aufruf des Generators über next()
die nächsthöhere Zahl geliefert.
Ende der Iteration bei Generatoren
Dieser Generator liefert genau 10 Zahlen. Beim elften next(
) wird eine StopIteration Exception geworfen, um das Ende anzuzeigen.
Fibonacci Generator
Die Methode zur Berechnung der Fibonacci-Folge kennst du ja schon. Sie hat allerdings zwei Probleme: Durch die doppelte Rekursion ist sie nicht sehr effizient und wird bei der Berechnung höherer Folgenelemente irgendwann einen Stack-Overflow produzieren.
Durch die Generatoren Technik lösen wir beide Probleme:
import sys
def fib_generator():
a,b = 0,1
while True:
yield a
a,b = b, a+b
fibo = fib_generator()
for _ in range(int(sys.argv[1])):
print(next(fibo))
Ich habe hier die Möglichkeit eingebaut, das zu berechnende Folgenelement beim Programmaufruf zu übergeben. Beim Aufruf mit python fib_gen.py 10 werden in der Liste sys.argv werden der Programmname in sys.argv[0]
und der erste Parameter in sys.argv[1]
übergeben. Bei weiteren Kommandozeilenparametern erweitert sich die Liste entsprechend. Dir fällt vielleicht der Unterstrich in der for-Schleife auf. Dieser wird als Konvention für Schleifenvariablen verwendet, wenn der Wert der Variable innerhalb der Schleife nicht benutzt wird.
Der Generator wird so oft abgerufen, wie über den Kommandozeilenparameter gewünscht. Die Berechnung des Listenelements ist mit a,b = b, a+b
sehr übersichtlich geworden: a
wird der Wert von b
zugewiesen und b
ist die Summe des Vorgängerelements und dem aktuellen Wert.
Da durch yield beide Variablen beim nächsten Aufruf wieder zur Verfügung stehen, ist die Fibonacci-Funktion damit korrekt iterativ implementiert.
List Comprehension
Mit einer List comprehension lassen sich generative Listen sehr einfach erzeugen. Ich zeig dir das mal an einer Liste der ersten 5 Quadratzahlen.
squares = []
for x in range(1,6):
squares.append(x**2)
print(squares)
Das ist nichts spektakuläres. In einer for
-Schleife werden die Quadratzahlen zur Liste dazu gefügt.
Eine List comprehension schafft dies mit einer einzigen Zeile Code
squares = [x **2 for x in range(1,6)]
print(squares)
Letztendlich wird hier die Schleife per Syntax in die Initialisierung der Liste verlagert, wodurch der Code deutlich kompakter wird.
In einer list comprehension kannst du auch konditional arbeiten. Wenn du nur die gerade Quadratzahlen in die Liste aufnehmen möchtest, setzt du das if einfach hinter die comprehension.
squares = [x **2 for x in range(1,100) if x % 2 == 0]
print(squares)
Da nur gerade Werte von x einen Modulo 2 von 0 haben, werden auch nur die in der comprehension betrachtet.
List Comprehension als Filter
list comprehension können auch zum Filtern einer Liste in eine neue nützlich sein. Als Beispiel nehme ich mal die schueler
Liste aus dem Datenbank Kapitel.
schueler = [
{'name':'Simpson','vorname': 'Bart'},
{'name':'Simpson','vorname':'Lisa'},
{'name':'van Houten','vorname':'Milhouse'},
{'name':'Wiggum','vorname':'Ralph'},
{'name':'Jones','vorname':'Jimbo'}
]
vornamen = [s['vorname'] for s in schueler if s['name'] == 'Simpson']
print(vornamen)
Hier filtert die comprehension aus der schueler Liste die Vornamen aus den einzelnen Dictionaries heraus und beachtet nur die, bei denen der Nachname ==
‚ Simpson‘ ist.
Da list comprehensions immer eine Schleife komplett durchlaufen, zählen sie nicht zu den Generatoren. Sie passen aber thematisch gut in diesen Artikel.
Im nächsten Kapitel will ich dir dann zeigen, wie du Webanwendungen mit Python programmieren kannst.