Comprehensions und Generatoren
Comprehensions sind syntaktische Konstrukte um bestehende zusammengesetzte Datenstrukturen (zB list
, set
, tuple
, dictionary
, …) transformiert oder gefiltert in eine neue zusammengesetzte Datenstruktur zu überführen.
Generatoren sind Erzeugungsvorschriften für iterierbare Datenstrukturen (ähnlich einer list, tuple, …). Generatoren werden auch als lazy Lists
bezeichnet.
Comprehension Syntax
Syntaktisch orientieren sich Comprehensions (auch in anderen Programmiersprachen) an der Mengenschreibweise der Mathematik. Beispielsweise sei die Menge
In Worten: Die Menge
enthält alle quadrierten Zahlen aus der Menge der natürlichen Zahlen, welche zwischen 10 und 20 liegen und gerade sind:
In Analogie zur Mengenschreibweise der Mathematik würde man in Python die Comprehension folgendermaßen niederschreiben:
m = [x ** 2 for x in range(10, 21) if x % 2 == 0]
print(m) # [100, 144, 196, 256, 324, 400]
Eine Comprehension in Python kann aus 4 Bestandteilen bestehen:
- Transformation bzw. Ausgabeausdruck
- Iterationsvariable
- Eingabemenge bzw. Eingabe-Datenstruktur
- Eigenschaft bzw. Filter (optional)
Prozedural vs Comprehension
Das Ergebnis einer Comprehension könnte auch als normale Schleife mit if/else Kontrollstrukturen erzeugt werden. Comprehensions bieten jedoch eine kompakte Ausdrucksweise und können dadurch mehrzeilige prozedurale Blöcke gut leserlich zusammenfassen.
Beispiel: Transformation
Eine Liste list
soll in eine neue Liste squared1
bzw. squared2
überführt werden. Dabei soll jedes Element der Liste list
quadriert werden (Transformation):
list = [1, 2, 3, 4, 5]
# Prozedural: 3 Zeilen
squared1 = []
for i in list:
squared1.append(i ** 2)
# Comprehension
squared2 = [i ** 2 for i in list]
print(squared1) # [1, 4, 9, 16, 25]
print(squared2) # [1, 4, 9, 16, 25]
Beispiel: Filter
Eine Liste list
soll in eine neue Liste even1
bzw. even2
überführt werden. Dabei soll für jedes Element der Liste list
geprüft werden, ob es durch 2 teilbar ist (Filter).
list = [1, 2, 3, 4, 5, 6]
# Prozedural: 4 Zeilen
even1 = []
for i in list:
if i % 2 == 0:
even1.append(i)
# Comprehension
even2 = [i for i in list if i % 2 == 0]
print(even1) # [2, 4, 6]
print(even2) # [2, 4, 6]
Comprehension für Set und Dictionary
Comprehensions funktionieren in Python neben list
auch für set
und dictionary
. Syntaktisch ändert sich prinzipiell nur die Art der Klammern.
Beispiel mit Set
Aus einem String sollen alle Zeichen (ohne Leerzeichen) in ein Set überführt werden:
text = "a sample string"
letters = {letter for letter in text if letter != ' '}
print(letters) # {'e', 'r', 'p', 'a', 'g', 'n', …
Beispiel mit Dictionary
Es soll die Häufigkeit der Zeichen eines Strings ermittelt werden. Wobei das jeweilige Zeichen als Schlüssel und die Häufigkeit als Wert eines Dictionaries definiert sein sollen:
text = "an example"
letters = {i: text.count(i) for i in text if i != ' '}
print(letters) # {'a': 2, 'n': 1, 'e': 2, 'x': 1, …
Generatoren
Generatoren sind Erzeugungsvorschriften für iterierbare Datenstrukturen. Die jeweiligen Elemente der Datenstruktur werden erst dann erzeugt, wenn sie abgefragt werden (lazy).
Die Datenstruktur, welche durch einen Generator erzeugt wird, kann unendlich groß definiert sein, benötigt aber kaum Speicherplatz.
Syntaktisch gibt es 2 Wege um Generatoren zu definieren:
- Generator Ausdruck (ähnlich der Comprehension)
- Generator Funktion (Nutzung von
yield
anstelle vonreturn
für Rückgabeparameter)
Generatoren werden zum Beispiel zur "live" Generierung von Trainingsdaten verwendet. Tensorflow bietet dazu eine eigene Dataset Methode.
Syntax: Mehrmaliges yield
def generator1():
yield "1. generation"
yield "2. generation"
yield "3. generation"
for g in generator1():
print(g)
# Ausgabe:
# 1. generation
# 2. generation
# 3. generation
Syntax: yield
in der Schleife
def generator2():
for i in (1, 2, 3):
yield str(i) + ". generation"
for g in generator2():
print(g)
# Ausgabe:
# 1. generation
# 2. generation
# 3. generation
Comprehension Syntax
generator3 = (str(i) + ". generation" for i in (1, 2, 3))
for g in generator3:
print(g)
# Ausgabe:
# 1. generation
# 2. generation
# 3. generation
Comprehension vs Generator
Die Comprehension soll eine Liste von 0 bis 9999… erzeugen und jedes Element quadrieren. Das Programm füllt den Hauptspeicher und endet gegebenenfalls mit einem MemoryError
:
squares = [n**2 for n in range(99999999999)]
Der Generator erzeugt in einer Endlosschleife das Quadrat von n, welches in jedem Durchlauf um 1 erhöht wird. Das Programm läuft problemlos mit konstantem und geringem Speicherverbrauch bis es manuell beendet wird:
def infinit_square(n=1):
while True:
yield n ** 2
n += 1
for i in infinit_square():
pass