Python: Generatoren und List Comprehension

Generatoren - Python Logo (CC-BY-SA The people from the Tango! project / Wikipedia)
Python Logo (CC-BY-SA The people from the Tango! project / Wikipedia)
1Python Programmierkurs
2Python: Methoden
3Kontrollstrukturen
4Strings in Python
5Container
6Objekte in Python
7Module
8Exceptions in Python
9Typkonvertierung
10Python und Dateien
11Datum und Zeit mit Python verarbeiten
12Multithreading
13Netzwerk in Python
14Logging in Python
15GPIO
16Automatische Tests
17Datenbanken mit Python
18Python: Generatoren und List Comprehension
19Python: Webseiten mit Flask
20Python virtuelle Umgebungen
21Interrupts & Signale
22NumPy
23Matplotlib
24match
25Reguläre Ausdrücke
26lambda Funktionen
27logging.config
28Decorators

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.

Schreibe einen Kommentar

Creative Commons License
Except where otherwise noted, the content on this site is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Olli Graf - raspithek.de
WordPress Cookie Hinweis von Real Cookie Banner