Python-Dictionary- und Set-Comprehensions
Dictionary-Comprehensions, Set-Comprehensions und Generator-Ausdrücke in Python meistern — mit Syntax, Beispielen und typischen Fallstricken.
Dictionary-Comprehensions, Set-Comprehensions und Generator-Ausdrücke erweitern die kompakte Syntax der List Comprehension auf andere Datenstrukturen. Dieses Kapitel erklärt jede Form ausführlich — Syntax, Filterung, Verschachtelung, praxisnahe Muster und wann man sie vermeiden sollte.
Dictionary Comprehensions
Eine Dictionary Comprehension erstellt ein neues dict aus einem beliebigen Iterable in einem einzigen Ausdruck. Anstatt eine for-Schleife zu schreiben, die bei jeder Iteration d[key] = value aufruft, beschreibt man die Zuordnung prägnant innerhalb geschweifter Klammern.
Syntax
new_dict = {key_expr: value_expr for item in iterable}Eine optionale if-Bedingung kann nach dem Iterable angefügt werden:
new_dict = {key_expr: value_expr for item in iterable if condition}| Teil | Rolle |
|---|---|
key_expr | Ausdruck, der jeden Schlüssel erzeugt |
value_expr | Ausdruck, der jeden Wert erzeugt |
item | Schleifenvariable — nimmt nacheinander jeden Wert aus iterable an |
if condition | Optionaler Filter — überspringt Elemente, bei denen condition False ist |
Einfaches Beispiel: Eine Quadrat-Map erstellen
squares = {x: x ** 2 for x in range(1, 6)}
print(squares)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}Die entsprechende for-Schleife:
squares = {}
for x in range(1, 6):
squares[x] = x ** 2Beide liefern dasselbe Ergebnis; die Comprehension-Form ist prägnanter und bringt die Absicht auf einen Blick zum Ausdruck.
Einträge filtern
Nur die Paare behalten, die eine Bedingung erfüllen:
prices = {"apple": 1.20, "banana": 0.50, "orange": 0.80, "grape": 2.50}
expensive = {item: price for item, price in prices.items() if price >= 1.00}
print(expensive)
# {'apple': 1.2, 'grape': 2.5}Werte transformieren
Eine Funktion oder einen arithmetischen Ausdruck auf jeden Wert anwenden:
prices = {"apple": 1.20, "banana": 0.50, "orange": 0.80}
# Apply a 10% discount and round to 2 decimal places
discounted = {item: round(price * 0.9, 2) for item, price in prices.items()}
print(discounted)
# {'apple': 1.08, 'banana': 0.45, 'orange': 0.72}Schlüssel und Werte tauschen
Ein Dictionary umkehren (funktioniert korrekt, wenn alle Werte eindeutig sind):
capitals = {"France": "Paris", "Germany": "Berlin", "Japan": "Tokyo"}
inverted = {city: country for country, city in capitals.items()}
print(inverted)
# {'Paris': 'France', 'Berlin': 'Germany', 'Tokyo': 'Japan'}Wenn Werte nicht eindeutig sind, überschreiben spätere Einträge frühere. Dies sollte nur verwendet werden, wenn man sicher ist, dass die Werte eine Eins-zu-eins-Zuordnung bilden.
Aus zwei Iterables mit zip() aufbauen
zip() koppelt Elemente aus zwei Sequenzen und macht es einfach, ein Dictionary aus separaten Schlüssel- und Wertelisten zu erstellen:
keys = ["name", "age", "city"]
values = ["Alice", 30, "Berlin"]
profile = {k: v for k, v in zip(keys, values)}
print(profile)
# {'name': 'Alice', 'age': 30, 'city': 'Berlin'}Dies entspricht dict(zip(keys, values)), aber die Comprehension-Form macht es einfach, gleichzeitig einen Filter oder eine Werttransformation hinzuzufügen.
Schlüssel normalisieren
Eine häufige Aufgabe in der Praxis ist das Bereinigen von Dictionary-Schlüsseln — zum Beispiel alle Schlüssel in Kleinbuchstaben umwandeln, wenn Daten aus verschiedenen Quellen zusammengeführt werden:
raw = {"Name": "Alice", "AGE": 30, "City": "Berlin"}
normalised = {k.lower(): v for k, v in raw.items()}
print(normalised)
# {'name': 'Alice', 'age': 30, 'city': 'Berlin'}Bedingter Wertausdruck (Ternary im Wert)
Im Wertbereich kann ein ternärer Ausdruck verwendet werden, um pro Element zwischen zwei Werten zu wählen:
scores = {"Alice": 95, "Bob": 62, "Carol": 78, "Dave": 45}
grades = {name: "pass" if score >= 70 else "fail" for name, score in scores.items()}
print(grades)
# {'Alice': 'pass', 'Bob': 'fail', 'Carol': 'pass', 'Dave': 'fail'}Verschachtelte Dictionary Comprehensions
Comprehensions können verschachtelt werden, um mehrstufige Daten zu verarbeiten. Zum Beispiel das Extrahieren eines Feldes aus einer Liste verschachtelter Dictionaries:
students = [
{"name": "Alice", "score": 95, "grade": "A"},
{"name": "Bob", "score": 82, "grade": "B"},
{"name": "Carol", "score": 91, "grade": "A"},
]
# Build a {name: score} mapping from the list
name_to_score = {s["name"]: s["score"] for s in students}
print(name_to_score)
# {'Alice': 95, 'Bob': 82, 'Carol': 91}Die Verschachtelung sollte flach bleiben. Wenn die Logik schwer nachvollziehbar wird, eine Hilfsfunktion extrahieren oder eine gewöhnliche for-Schleife verwenden.
Set Comprehensions
Eine Set Comprehension erstellt ein set in einem einzigen Ausdruck. Da Sets nur eindeutige Elemente enthalten, werden Duplikate automatisch entfernt.
Syntax
new_set = {expression for item in iterable}
new_set = {expression for item in iterable if condition}Der einzige visuelle Unterschied zu einer Dict Comprehension ist das Fehlen des Doppelpunkts — es gibt einen Ausdruck, kein key: value-Paar.
Einfaches Beispiel: Eindeutige Quadrate
numbers = [1, 2, 2, 3, 3, 3, 4]
unique_squares = {x ** 2 for x in numbers}
print(unique_squares)
# {1, 4, 9, 16}Die Ausgabereihenfolge ist nicht garantiert — Sets sind ungeordnet, daher sollte man sich nicht auf eine bestimmte Anzeigereihenfolge verlassen.
Deduplizieren beim Transformieren
Ein verbreitetes Muster ist das Normalisieren von Strings und das Deduplizieren in einem Schritt:
tags = ["Python", "python", "PYTHON", "Data", "data", "DATA"]
unique_tags = {tag.lower() for tag in tags}
print(unique_tags)
# {'python', 'data'}Filtern mit einer Bedingung
words = ["cat", "elephant", "dog", "rhinoceros", "ant"]
long_words = {w for w in words if len(w) > 4}
print(long_words)
# {'elephant', 'rhinoceros'}Gemeinsame Elemente finden
Set Comprehensions lassen sich gut mit Mengenoperationen kombinieren:
list_a = [1, 2, 3, 4, 5, 5, 6]
list_b = [4, 5, 6, 7, 8]
set_a = {x for x in list_a}
set_b = {x for x in list_b}
common = set_a & set_b
print(common)
# {4, 5, 6}Für eine einfache Konvertierung ohne Transformation ist set(list_a) einfacher. Eine Set Comprehension sollte verwendet werden, wenn beim Konvertieren gefiltert oder transformiert werden soll.
Generator-Ausdrücke
Ein Generator-Ausdruck sieht aus wie eine List Comprehension, verwendet aber runde Klammern statt eckiger. Anstatt die gesamte Sammlung auf einmal im Speicher aufzubauen, erzeugt er einen Wert nach dem anderen, bei Bedarf (lazy).
Syntax
gen = (expression for item in iterable)
gen = (expression for item in iterable if condition)Warum einen Generator-Ausdruck verwenden?
| Szenario | List Comprehension | Generator-Ausdruck |
|---|---|---|
| Ergebnis muss mehrfach iteriert werden | Ja | Nein — Generatoren sind nach einem Durchlauf erschöpft |
Wahlfreier Zugriff per Index (result[3]) nötig | Ja | Nein |
Ergebnis wird an sum(), max(), any() etc. übergeben | Funktioniert, aber alloziert eine Liste | Bevorzugt — streamt Werte ohne Liste aufzubauen |
| Sehr große oder unendliche Sequenz | Kann den Speicher erschöpfen | Effizient — ein Wert nach dem anderen |
Einfaches Beispiel
# List comprehension — builds the entire list in memory
squares_list = [x ** 2 for x in range(1, 6)]
print(squares_list) # [1, 4, 9, 16, 25]
# Generator expression — yields values one at a time
squares_gen = (x ** 2 for x in range(1, 6))
print(squares_gen) # <generator object <genexpr> at 0x...>
print(list(squares_gen)) # [1, 4, 9, 16, 25]Das Generator-Objekt selbst ist nicht die Liste — man verbraucht es durch Iteration oder Übergabe an eine Funktion.
Direkt an eingebaute Funktionen übergeben
Wenn ein Generator-Ausdruck als einziges Argument an eine Funktion übergeben wird, können die zusätzlichen runden Klammern weggelassen werden:
total = sum(x ** 2 for x in range(1, 1001))
print(total) # 333833500
maximum = max(len(word) for word in ["apple", "banana", "kiwi"])
print(maximum) # 6
any_negative = any(x < 0 for x in [1, -2, 3])
print(any_negative) # TrueSpeichervorteil
Bei großen Datenmengen vermeidet ein Generator-Ausdruck das Laden aller Daten in den RAM:
# Simulate a large log file as a list of strings
log_lines = [f"ERROR line {i}" if i % 100 == 0 else f"INFO line {i}" for i in range(1_000_000)]
# Generator scans lines without building an intermediate list
error_count = sum(1 for line in log_lines if line.startswith("ERROR"))
print(error_count) # 10000Generatoren sind nach einem Durchlauf erschöpft
Dies ist der häufigste Fallstrick:
gen = (x * 2 for x in range(5))
print(list(gen)) # [0, 2, 4, 6, 8]
print(list(gen)) # [] — the generator is now emptyWenn das Ergebnis mehr als einmal iteriert werden muss, entweder eine List Comprehension verwenden oder den Generator neu erstellen.
Generator-Ausdrücke verketten
Generator-Ausdrücke können ohne Zwischenlisten kombiniert werden. Jede Stufe zieht Werte aus der vorherigen:
numbers = range(1, 11)
# Stage 1: filter even numbers
evens = (x for x in numbers if x % 2 == 0)
# Stage 2: square them
even_squares = (x ** 2 for x in evens)
print(list(even_squares)) # [4, 16, 36, 64, 100]Es wird keine Zwischenliste erstellt — Werte fließen einzeln durch die Pipeline.
Die richtige Form wählen
| Sie benötigen… | Verwenden Sie |
|---|---|
| Eine Liste transformierter/gefilterter Werte | [expr for item in it] |
| Ein Dictionary aus Wertepaaren | {k: v for item in it} |
| Eine Sammlung ohne Duplikate | {expr for item in it} |
| Speichereffiziente Einmal-Iteration | (expr for item in it) |
| Komplexe, mehrzeilige Logik pro Element | Gewöhnliche for-Schleife |
Häufige Fallstricke
Dict Comprehension vs. Set Comprehension. Beide verwenden geschweifte Klammern {}. Der Unterschied liegt darin, ob man key: value (dict) oder einen einzelnen expression (set) schreibt. Ein leeres {} erstellt immer ein leeres dict, kein leeres Set — für ein leeres Set set() verwenden.
d = {} # empty dict
s = set() # empty set — NOT {}Überschriebene Schlüssel. Wenn der Schlüsselausdruck Duplikate erzeugt, überschreiben spätere Werte stillschweigend frühere:
data = [("a", 1), ("b", 2), ("a", 99)]
d = {k: v for k, v in data}
print(d) # {'a': 99, 'b': 2} — first 'a' is goneVariablenbereich. In Python 3 ist die Schleifenvariable in einer Comprehension lokal zur Comprehension und dringt nicht in den umgebenden Bereich vor:
x = "original"
result = {x: x.upper() for x in ["a", "b", "c"]}
print(x) # 'original' — comprehension's x did not overwrite thisLesbarkeitsgrenze. Wenn mehr als eine if-Bedingung benötigt wird oder der Ausdruck lang ist, ist eine gewöhnliche for-Schleife mit beschreibenden Variablennamen in der Regel übersichtlicher:
# Hard to read
result = {k: v for k, v in data.items() if k.startswith("user_") if v is not None}
# Easier to read
result = {}
for k, v in data.items():
if k.startswith("user_") and v is not None:
result[k] = vVerwandte Themen
- List Comprehension — die Grundlage: Syntax, Filterung, verschachtelte Schleifen und wann eine gewöhnliche
for-Schleife besser ist - Python Dictionaries — Dictionary-Grundlagen, Erstellung und Zugriff
- Python Sets — Set-Erstellung, Operationen und Anwendungsfälle
- Loop Dictionaries — alle Techniken zum Iterieren über Dictionaries
- Python Iterators — wie das Iterator-Protokoll von Python unter der Haube funktioniert