23 Iteratoren 

In den vorangegangenen Kapiteln war bereits häufiger die Rede von Begriffen wie »iterieren« oder »iterierbares Objekt«. Ohne diese genau definiert zu haben, ist intuitiv klar, dass damit das sukzessive Betrachten der einzelnen Elemente einer entsprechenden Instanz gemeint ist. Es kann beispielsweise mithilfe einer for-Schleife über eine Liste »iteriert« werden:
>>> for x in [1,2,3,4]:
... print(x)
...
1
2
3
4
In diesem Kapitel möchten wir den Begriff des Iterierens auf solide Füße stellen, indem wir Ihnen das zentrale Konzept der Generatoren und Iteratoren vorstellen. Zuvor besprechen wir mit den Comprehensions eine komfortable Syntax zur Erzeugung von Instanzen iterierbarer Objekte wie Listen, Dictionarys und Sets.
23.1 Comprehensions 

In diesem Abschnitt stürzen wir uns auf ein interessantes Feature von Python, die sogenannten Comprehensions. Das sind spezielle Anweisungen, mit denen Sie eine neue Liste bzw. ein neues Dictionary oder Set mit generischem Inhalt erzeugen. Das bedeutet, Sie geben eine Erzeugungsvorschrift an, nach der die jeweilige Instanz mit Werten gefüllt wird.
Während List Comprehensions bereits seit Längerem in Python existieren, wurden Dict Comprehensions und Set Comprehensions erst mit Python 3.0 eingeführt.
23.1.1 List Comprehensions 

Es ist ein häufig auftretendes Problem, dass man aus den Elementen einer bestehenden Liste nach einer bestimmten Berechnungsvorschrift eine neue Liste erstellen möchte. Bislang würden Sie dies entweder umständlich in einer for-Schleife erledigen oder die Built-in Functions map und filter einsetzen. Letzteres ist zwar relativ kurz, bedarf jedoch einer Funktion, die auf jedes Element der Liste angewandt wird. Das ist umständlich und ineffizient.
Python unterstützt eine flexiblere Syntax, die für diesen Zweck geschaffen wurde: die sogenannten List Comprehensions. Die folgende List Comprehension erzeugt aus einer Liste mit ganzen Zahlen eine neue Liste, die die Quadrate dieser Zahlen enthält:
>>> lst = [1,2,3,4,5,6,7,8,9]
>>> [x**2 for x in lst]
[1, 4, 9, 16, 25, 36, 49, 64, 81]
Eine List Comprehension wird in eckige Klammern gefasst und besteht zunächst aus einem Ausdruck, gefolgt von beliebig vielen for/in-Bereichen. Ein for/in-Bereich lehnt sich an die Syntax der for-Schleife an und legt fest, mit welchem Bezeichner über welche Liste iteriert wird – in diesem Fall mit dem Bezeichner x über die Liste lst. Der angegebene Bezeichner kann im Ausdruck zu Beginn der List Comprehension verwendet werden. Das Ergebnis einer List Comprehension ist eine neue Liste, die als Elemente die Ergebnisse des Ausdrucks in jedem Iterationsschritt enthält. Die Funktionsweise der oben dargestellten List Comprehension lässt sich folgendermaßen zusammenfassen:
Für jedes Element x der Liste lst bilde das Quadrat von x, und füge das Ergebnis in die Ergebnisliste ein.
Dies ist die einfachste Form der List Comprehension. Der for/in-Bereich lässt sich um eine Fallunterscheidung erweitern, sodass nur bestimmte Elemente in die neue Liste übernommen werden. So könnten wir die obige List Comprehension beispielsweise dahingehend erweitern, dass nur die Quadrate gerader Zahlen gebildet werden:
>>> lst = [1,2,3,4,5,6,7,8,9]
>>> [x**2 for x in lst if x%2 == 0]
[4, 16, 36, 64]
Dazu wird der for/in-Bereich um das Schlüsselwort if erweitert, auf das eine Bedingung folgt. Nur wenn diese Bedingung True ergibt, wird das berechnete Element in die Ergebnisliste aufgenommen. Diese Form der List Comprehension lässt sich also folgendermaßen beschreiben:
Für jedes Element x der Liste lst – sofern es sich bei x um eine gerade Zahl handelt – bilde das Quadrat von x, und füge das Ergebnis in die Ergebnisliste ein.
Als nächstes Beispiel soll eine List Comprehension dazu verwendet werden, zwei als Listen dargestellte dreidimensionale Vektoren zu addieren. Die Addition von Vektoren erfolgt koordinatenweise, also in unserem Fall Element für Element:
>>> v1 = [1, 7, -5]
>>> v2 = [-9, 3, 12]
>>> [v1[i] + v2[i] for i in range(3)]
[-8, 10, 7]
Dazu wird eine von range erzeugte Liste von Indizes in der List Comprehension durchlaufen. In jedem Durchlauf werden die jeweiligen Koordinaten addiert und an die Ergebnisliste angehängt.
Es wurde bereits gesagt, dass eine List Comprehension beliebig viele for/in-Bereiche haben kann. Diese können wie verschachtelte for-Schleifen betrachtet werden. Im Folgenden möchten wir ein Beispiel besprechen, in dem diese Eigenschaft von Nutzen ist. Zunächst definieren wir zwei Listen:
>>> lst1 = ["A", "B", "C"]
>>> lst2 = ["D", "E", "F"]
Eine List Comprehension soll nun eine Liste erstellen, die alle möglichen Buchstabenkombinationen enthält, die gebildet werden können, indem man zunächst einen Buchstaben aus lst1 und dann einen aus lst2 wählt. Die Kombinationen sollen jeweils als Tupel in der Liste stehen:
>>> [(a,b) for a in lst1 for b in lst2]
[('A', 'D'), ('A', 'E'), ('A', 'F'), ('B', 'D'), ('B', 'E'),
('B', 'F'), ('C', 'D'), ('C', 'E'), ('C', 'F')]
Diese List Comprehension kann folgendermaßen beschrieben werden:
Für jedes Element a der Liste lst1 gehe über alle Elemente b von lst2, und füge jeweils das Tupel (a,b) in die Ergebnisliste ein.
List Comprehensions bieten einen interessanten und eleganten Weg, komplexe Operationen platzsparend zu schreiben. Viele Probleme, bei denen List Comprehensions zum Einsatz kommen, könnten auch durch die Built-in Functions map, filter oder durch eine Kombination der beiden gelöst werden, jedoch sind List Comprehensions zumeist besser lesbar und führen zu einem übersichtlicheren Quellcode.
23.1.2 Dict Comprehensions 

Seit Version 3.0 bietet Python einen zu den List Comprehensions analogen Weg, um ein Dictionary zu erzeugen. Dies nennt sich Dictionary Comprehension bzw. kurz Dict Comprehension.
Der Aufbau einer Dict Comprehension ist ähnlich wie der einer List Comprehension, weswegen wir direkt mit einem Beispiel einsteigen:
>>> namen = ["Donald", "Dagobert", "Daisy", "Gustav"]
>>> {k:len(k) for k in namen}
{'Donald': 6, 'Dagobert': 8, 'Daisy': 5, 'Gustav': 6}
>>> {k:len(k) for k in namen if k[0] == "D"}
{'Donald': 6, 'Dagobert': 8, 'Daisy': 5}
Hier wurde mithilfe einer Dict Comprehension ein Dictionary erzeugt, das eine vorgegebene Liste von Strings als Schlüssel und die Längen des jeweiligen Schlüssel-Strings als Wert enthält.
Beim Betrachten des Beispiels fallen sofort zwei Unterschiede zu den List Comprehensions auf:
- Im Gegensatz zu einer List Comprehension wird eine Dict Comprehension in geschweifte Klammern gefasst.
- Bei einer Dict Comprehension muss in jedem Durchlauf der Schleife ein Schlüssel-Wert-Paar zum Dictionary hinzugefügt werden. Dieses steht am Anfang der Comprehension, wobei Schlüssel und Wert durch einen Doppelpunkt voneinander getrennt sind.
Sonst können Sie eine Dict Comprehension verwenden, wie Sie es bereits von List Comprehensions her kennen. Beide Typen lassen sich auch gemeinsam nutzen. Dazu noch ein Beispiel:
>>> lst1 = ["A", "B", "C"]
>>> lst2 = [2, 4, 6]
>>> {k:[k*i for i in lst2] for k in lst1}
{'A': ['AA', 'AAAA', 'AAAAAA'], 'B': ['BB', 'BBBB', 'BBBBBB'], 'C': ['CC',
'CCCC', 'CCCCCC']}
Dieser Code erzeugt ein Dictionary, das zu jedem Schlüssel mithilfe einer List Comprehension eine Liste als Wert erzeugt, die jeweils das Zwei-, Vier- und Sechsfache des Schlüssels enthält.
23.1.3 Set Comprehensions 

Der dritte wichtige Datentyp, für den ebenfalls eine Comprehension-Syntax existiert, ist das Set. Eine Set Comprehension wird wie eine Dict Comprehension in geschweifte Klammern eingefasst. Im Gegensatz zur Dict Comprehension fehlen allerdings der Doppelpunkt und der dahinter angegebene Wert:
>>> lst = [1,2,3,4,5,6,7,8,9]
>>> {i**2 for i in lst}
{64, 1, 4, 36, 9, 16, 49, 81, 25}
Eine Set Comprehension funktioniert – abgesehen von den geschweiften Klammern – völlig analog zur List Comprehension. Es bedarf also keiner weiteren Beispiele, um sie erfolgreich einzusetzen.