Comprehensions — eine Schleife in einer Zeile
List-, Dictionary- und Set-Comprehensions, der Unterschied zwischen einem filternden if und einem wählenden if/else, und wann eine Comprehension die falsche Wahl ist.
Wie möchtest du lernen?
Das Problem, das wir lösen
Eine Form von Code ist in den letzten sechs Kapiteln immer wieder aufgetaucht:
marks = [72, 45, 90, 33]
doubled = []
for mark in marks:
doubled.append(mark * 2)
print(doubled)
doubled = [mark * 2 for mark in marks]
print(doubled)[144, 90, 180, 66]
[144, 90, 180, 66]Die vier Zeilen oben und die eine Zeile unten machen genau dasselbe.
Drei dieser vier Zeilen sind nicht die eigentliche Arbeit, sondern nur Buchhaltung — eine leere Liste anlegen, die Quelle durchlaufen, jedes Mal anhängen. Die einzige echte Entscheidung ist mark * 2. Alles andere ist jedes Mal dasselbe.
Eine Comprehension nimmt diese Wiederholung weg. Sie ist keine neue Fähigkeit — sie ist das, was du schon schreiben konntest, nur in weniger Worten. Und daher kommt auch ihre Grenze, und darum geht der letzte Abschnitt dieses Kapitels: Kürze das, was durch das Kürzen klarer wird, nicht alles.
Am Ende dieses Kapitels kannst du
- Eine List Comprehension schreiben und lesen
- Mit
iffiltern und mitif/elsewählen - Dictionary- und Set-Comprehensions schreiben
- Sagen, wann eine Comprehension die falsche Wahl ist
- Eine unlesbare Comprehension wieder in eine Schleife zerlegen
Voraussetzungen: Verschachtelte Daten — eine Liste von Dictionaries.
Die Form
[ result for item in source ]Hier ist result das, was du bauen willst, item ist jedes Mal eine Sache, und source ist die Liste (oder andere Sammlung), aus der die Sachen kommen.
Ausgesprochen ist es ein ganz normaler Satz: „`mark 2 für jedes mark in marks`“*.
Eine Merkhilfe: Lies zuerst den for-Teil, dann den Anfang. Wenn du weißt, woher die Sachen kommen, siehst du leicht, was gebaut wird.
Filtern
Ein if am Ende behält nur die Sachen, die passen:
marks = [72, 45, 90, 33]
passed = [mark for mark in marks if mark >= 40]
print(passed)[72, 45, 90]Hier ist der erste Teil nur mark — keine Rechnung, einfach so, wie es ist. Filtern und Umwandeln geht zusammen:
names = [" rafi ", "AHMED", " dia"]
clean = [name.strip().lower() for name in names if name.strip()]
print(clean)['rafi', 'ahmed', 'dia']Die Bedingung ist name.strip() — überhaupt kein Vergleich. Erinner dich an die Wahrheitsregel aus Kapitel acht: Ein leerer String ist falsch. Wer also nur Leerzeichen getippt hat, fliegt raus.
if am Ende versus if am Anfang
Zwei verschiedene Dinge, und der Unterschied ist die Position:
marks = [72, 33, 90]
labels = ["pass" if m >= 40 else "fail" for m in marks]
print(labels)['pass', 'fail', 'pass']Ein if am Ende filtert — manche Sachen erreichen das Ergebnis nie, das Ergebnis kann also kürzer werden.
Ein if ... else am Anfang wählt — jedes Element liefert etwas, die Länge bleibt also gleich. Das ist die einzeilige Bedingung aus Kapitel fünfzehn, in einer Comprehension.
Ändert sich die Länge? Diese Frage ist der einfachste Weg, die beiden zu unterscheiden.
Auch Dictionaries und Sets
Dieselbe Form, andere Klammern:
marks = {"rafi": 72, "ahmed": 45, "bilal": 90}
passed = {name: mark for name, mark in marks.items() if mark >= 50}
print(passed)
doubled = {name: mark * 2 for name, mark in marks.items()}
print(doubled){'rafi': 72, 'bilal': 90}
{'rafi': 144, 'ahmed': 90, 'bilal': 180}Geschweifte Klammern und ein Doppelpunkt ergeben eine Dictionary-Comprehension. marks.items() liefert bei jeder Runde ein Tupel, und name, mark packt es aus, genau wie in einer Schleife.
Dieselben geschweiften Klammern ohne Doppelpunkt ergeben ein Set:
orders = [
{"item": "pen"},
{"item": "bag"},
{"item": "pen"},
]
items = {order["item"] for order in orders}
print(sorted(items))['bag', 'pen']Die „wie viele verschiedene“-Frage aus Kapitel sechzehn in einer Zeile. pen kommt zweimal an und erscheint einmal.
Zahlen in Strings — das hast du schon gesehen
Diese Zeile tauchte in Kapitel dreizehn auf, mit dem Versprechen, dass die Erklärung noch kommt:
marks = [72, 45, 90]
print(", ".join(str(m) for m in marks))
print([str(m) for m in marks])72, 45, 90
['72', '45', '90']Jetzt kannst du sie komplett lesen. str(m) for m in marks ist eine Comprehension ohne die eckigen Klammern — diese Form heißt Generatorausdruck, und statt die ganze Liste auf einmal zu bauen, reicht er join ein Element nach dem anderen. Die Klammern dürfen weggelassen werden, wenn es das einzige Argument einer Funktion ist, und bei großen Daten spart das Speicher.
Wann du besser keine schreibst
Comprehensions schreiben zu können heißt nicht, dass du es solltest. Schau dir das an:
rows = [["1", "2"], ["3", "x"]]
numbers = [int(c) for row in rows for c in row if c.isdigit()]
print(numbers)[1, 2, 3]Die Zeile ist korrekt. Aber sie hat zwei fors und ein if, und du musst die Reihenfolge im Kopf behalten: Die fors liest man von außen nach innen, so wie verschachtelte Schleifen geschrieben würden. Sechs Monate später ist diese Zeile Arbeit zum Lesen.
Als Schleife:
rows = [["1", "2"], ["3", "x"]]
numbers = []
for row in rows:
for c in row:
if c.isdigit():
numbers.append(int(c))
print(numbers)[1, 2, 3]Mehr Zeilen, weniger Denken. Das Ziel von Code ist nicht, kurz zu sein, sondern klar — und meistens macht Kürzen ihn klarer, aber nicht immer.
Als Faustregel: ein for und höchstens ein if — eine Comprehension. Mehr als das — eine Schleife. Und stecke nie ein print oder ein append in eine Comprehension; sie ist zum Bauen eines Werts da, nicht zum Arbeiten.
Ein vollständiges Beispiel
views.py:
# One list of records in, three different views out
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
{"customer": "rafi", "item": "ink", "quantity": 2, "price": 120.0},
{"customer": "dia", "item": "pen", "quantity": 5, "price": 15.0},
]
totals = [order["quantity"] * order["price"] for order in orders]
print("Totals :", totals)
big = [order["item"] for order in orders if order["quantity"] * order["price"] > 100]
print("Over 100 :", big)
items = {order["item"] for order in orders}
print("Distinct :", sorted(items))
by_item = {order["item"]: order["price"] for order in orders}
print("Price list:", by_item)
print("Grand :", sum(totals))Totals : [45.0, 850.0, 240.0, 75.0]
Over 100 : ['bag', 'ink']
Distinct : ['bag', 'ink', 'pen']
Price list: {'pen': 15.0, 'bag': 850.0, 'ink': 120.0}
Grand : 1210.0Dieselben Daten wie in Kapitel siebzehn, wobei das, was dort eine Schleife war, jetzt meist eine Zeile ist.
Drei Dinge, die auffallen.
totals und sum(totals) sind getrennte Zeilen. Der Gesamtwert hätte auch sum(order["quantity"] * order["price"] for order in orders) sein können, aber dann existierten die einzelnen Werte nicht. Ein Zwischenname ist oft mehr wert als ein Trick in einer Zeile.
Dieselbe Rechnung kommt in big zweimal vor — einmal in der Bedingung, und im Ergebnis wäre sie nochmal nötig. Hier nimmt das Ergebnis nur den Namen, also geht nichts verloren; aber wenn du beide brauchst, ist das ein guter Grund, die Comprehension zu verlassen und eine Schleife zu schreiben, in der der Wert einmal berechnet und einem Namen gegeben wird.
by_item hat einen Datensatz verloren. Vier Bestellungen, drei Paare im Ergebnis — pen erscheint zweimal, und nach der Regel aus Kapitel fünfzehn hat der spätere stillschweigend den früheren ersetzt. Hier haben beide pen denselben Preis, also ist nichts passiert; bei verschiedenen Preisen wäre es stillschweigend falsch. Eine Comprehension erzeugt das Problem nicht, aber ihre Kürze gibt ihm weniger Gelegenheit, bemerkt zu werden.
Wenn etwas schiefgeht
SyntaxError, weil if und else am Ende stehen [m for m in marks if m >= 40 else 0] ist ungültig. Das filternde if steht am Ende und hat kein else; das wählende if ... else steht vorne: [m if m >= 40 else 0 for m in marks].
Das Ergebnis ist voller None Etwas in der Comprehension liefert nichts zurück — zum Beispiel [items.append(x) for x in other]. append gibt None zurück, also ist das Ergebnis eine Liste von None. Eine Comprehension baut Werte; zum Arbeiten schreibst du eine normale Schleife.
NameError — die Variable der Comprehension ist außerhalb weg Nach [m * 2 for m in marks] kannst du m nicht mehr benutzen. In Kapitel elf hat die Variable einer for-Schleife die Schleife überlebt; die einer Comprehension nicht — sie lebt nur darin.
Eine Dictionary-Comprehension hat weniger Paare ergeben als erwartet Derselbe Key wurde mehr als einmal erzeugt, und der spätere hat den früheren ersetzt. Vergleiche mit len().
Ich kann die Zeile nicht lesen Das ist Grund genug. Zerleg sie in eine Schleife — der Geschwindigkeitsunterschied spielt für Anfänger selten eine Rolle, und die nächste Person, die sie liest, wahrscheinlich du, wird dankbar sein.
Schritt 4 von 6 — Vorhersagen
Prüfe dein Verständnis
Am Ende steht ein if. Was wird ausgegeben?
marks = [72, 45, 90, 33]
passed = [mark for mark in marks if mark >= 40]
print(passed)- A[72, 45, 90, 33]
- B[True, True, True, False]
- C[33]
- D[72, 45, 90]
Diesmal steht das if vorne, mit einem else. Was wird ausgegeben?
marks = [72, 33, 90]
labels = ["pass" if m >= 40 else "fail" for m in marks]
print(labels)- A['pass', 'fail', 'pass']
- B['pass', 'pass']
- C[72, 90]
- D['pass', 'fail']
append wird in einer Comprehension aufgerufen. Was geben die beiden Zeilen aus?
source = ["pen", "bag"]
target = []
result = [target.append(item) for item in source]
print(target)
print(result)- A['pen', 'bag'] [None, None]
- BEin `TypeError`
- C['pen', 'bag'] ['pen', 'bag']
- D[] ['pen', 'bag']
Zum Antworten brauchst du ein Konto
Melde dich an, um deine Antworten zu prüfen
Die Fragen stehen oben, und sie im Kopf durchzuarbeiten ist der wichtige Teil. Melde dich an, um die Antworten, die Erklärungen und die dreistufigen Hinweise zu sehen.
Jetzt bist du dran
Schreib eine Datei namens clean.py mit einer Liste von unordentlichen Namen — manche mit extra Leerzeichen, manche großgeschrieben, und mindestens einen, der ganz leer ist.
Benutze Comprehensions und baue:
- Eine Liste mit bereinigten Namen — getrimmt, kleingeschrieben, ohne die leeren
- Eine Liste mit der Länge jedes Namens
- Eine Liste mit den Namen, die länger als fünf Zeichen sind
- Ein Dictionary mit
{name: length} - Ein Set der Anfangsbuchstaben
Dann zwei Experimente:
- Schreib Nummer 1 nochmal als normale
for-Schleife und prüfe mit==, dass beide Ergebnisse gleich sind - Schreib eine Comprehension mit zwei
fors und einemifund zerleg sie dann in eine Schleife. Was ist leichter zu lesen?
Diese letzte Frage hat keine „richtige“ Antwort, und genau darauf kommt es an. Sie zu stellen, ist die eigentliche Lektion dieses Kapitels.
Schritt 6 von 6
Herausforderung — das Kapitel-Quiz
Zehn Fragen von leicht bis schwer. Die letzten sind absichtlich knifflig.
Melde dich an, um das Quiz zu machen