Zum Inhalt

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 M wie folgt definiert:

M={x2 | xN, 10x20, x mod 2=0}

In Worten: Die Menge M enthält alle quadrierten Zahlen aus der Menge der natürlichen Zahlen, welche zwischen 10 und 20 liegen und gerade sind: M={100,144,196,256,324,400}

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)

Comprehension

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 von return 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