Verschachtelte Daten — eine Liste von Dictionaries
Die Form, in der echte Daten ankommen: Dictionaries in Listen, Listen in Dictionaries. Hineingreifen, Gruppieren und warum copy() hier nicht reicht.
Wie möchtest du lernen?
Das Problem, das wir lösen
Die letzten Kapitel haben vier Container gelehrt — Listen, Tupel, Dictionaries, Sets. Jedes Beispiel benutzte einen davon, eine Ebene tief.
Echte Daten kommen so nicht an. Eine Liste von Bestellungen sieht so aus:
customer item quantity price
rafi pen 3 15.0
ahmed bag 1 850.0Hier gibt es zwei Ebenen. Außen eine Sammlung — die Anzahl der Bestellungen ist unbekannt, also eine Liste. Innen ist jede Bestellung ein Datensatz, bei dem jeder Teil einen Namen hat — also ein Dictionary.
Was aus einer API zurückkommt, was du beim Lesen einer CSV bekommst, was eine Datenbank liefert: fast immer diese Form, eine Liste von Dictionaries. Das heutige Kapitel führt keinen neuen Container ein. Es geht darum, die vorhandenen ineinander zu legen und die Fehler zu erkennen, die daraus folgen.
Am Ende dieses Kapitels kannst du
- In eine Liste von Dictionaries hineingreifen und sie durchlaufen
- Einen tiefen
KeyErroroderIndexErrorlesen und sagen, auf welcher Ebene etwas schiefging - Eine Liste von Dictionaries gruppieren und zusammenfassen
- Unvollständige Datensätze mit dem Standardwert von
.get()behandeln - Erklären, warum
copy()bei verschachtelten Daten nicht reicht
Voraussetzungen: Set — eine Sammlung ohne Duplikate.
Die Form erkennen
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
]
print(len(orders))
print(orders[0])
print(orders[0]["customer"])
print(orders[1]["price"])2
{'customer': 'rafi', 'item': 'pen', 'quantity': 3, 'price': 15.0}
rafi
850.0Die Zeile orders[0]["customer"] ist die Kernkompetenz dieses Kapitels, und man liest sie von links nach rechts, einen Schritt nach dem anderen:
orders— eine Listeorders[0]— ihr erstes Element, ein Dictionaryorders[0]["customer"]— dercustomer-Wert dieses Dictionaries
Im Zweifel: stehenbleiben und den mittleren Schritt ausgeben. print(orders[0]) zu schreiben, um zu sehen, dass es ein Dictionary ist, ist eine Gewohnheit, die die Hälfte des Ärgers mit verschachtelten Daten wegnimmt.
Mit einer Schleife durchlaufen
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
]
for order in orders:
total = order["quantity"] * order["price"]
print(f"{order['customer']:<8} {order['item']:<6} {total:>8.2f}")rafi pen 45.00
ahmed bag 850.00for order in orders liefert bei jeder Runde ein Dictionary, und darin greifst du per Namen auf Dinge zu. Nirgends gibt es Index-Rechnerei.
Anführungszeichen in einem f-string. f"{order['customer']}" benutzt innen einfache Anführungszeichen, weil der f-string selbst mit doppelten geschrieben ist. Nimm innen und außen dieselbe Sorte, dann kann Python nicht erkennen, wo der Text endet. Eine Sorte innen, die andere außen.Unvollständige Datensätze
In echten Daten hat nicht jeder Datensatz jeden Key:
orders = [{"customer": "rafi"}]
print(orders[0]["discount"])KeyError: 'discount'Der KeyError sagt, dass der Key fehlt, aber nicht welcher Datensatz ihn vermisst. Wenn das bei einer Bestellung von zwölftausend passiert, ist es mühsam, sie zu finden. Ein Durchlauf mit enumerate liefert dir die Nummer jedes Datensatzes, sodass du den ausgeben kannst, dem der Key fehlt:
orders = [
{"customer": "rafi", "discount": 5},
{"customer": "ahmed", "discount": 0},
{"customer": "dia"},
]
for i, order in enumerate(orders):
if "discount" not in order:
print("record", i, "has no discount:", order)record 2 has no discount: {'customer': 'dia'}Jetzt weißt du, dass das Problem in Datensatz 2 liegt (das Zählen beginnt bei null), und wie dieser Datensatz aussieht.
Wenn ein fehlender Key normal ist, .get():
orders = [{"customer": "rafi"}]
print(orders[0].get("discount", 0))0Auch die Regel aus Kapitel fünfzehn gilt hier: eckige Klammern, wenn der Key da sein sollte, .get() wenn sein Fehlen normal ist. Die Versuchung ist bei verschachtelten Daten stärker, denn .get() kann jeden Fehler zum Schweigen bringen — und dann werden unvollständige Daten stillschweigend zu null und gehen in deine Berechnungen ein.
Die andere Form — Listen in einem Dictionary
by_customer = {
"rafi": ["pen", "ink"],
"ahmed": ["bag"],
}
by_customer["rafi"].append("bottle")
by_customer["dia"] = ["eraser"]
print(by_customer)
for customer, items in by_customer.items():
print(f"{customer}: {len(items)} item(s) - {', '.join(items)}"){'rafi': ['pen', 'ink', 'bottle'], 'ahmed': ['bag'], 'dia': ['eraser']}
rafi: 3 item(s) - pen, ink, bottle
ahmed: 1 item(s) - bag
dia: 1 item(s) - eraserDie Zeile by_customer["rafi"].append("bottle") sind zwei Schritte in einem: by_customer["rafi"] zieht eine Liste heraus, und dann hängt append an diese Liste an.
Diese Form löst ein Problem aus Kapitel fünfzehn, wo ein Key nur einmal vorkommen konnte und eine Person deshalb nicht mehrere Datensätze haben konnte. Mache den Wert zu einer Liste, dann geht das.
Gruppieren — das nützlichste Muster
Aus einer Liste von Dictionaries „wer was gekauft hat“ machen:
orders = [
{"customer": "rafi", "item": "pen"},
{"customer": "ahmed", "item": "bag"},
{"customer": "rafi", "item": "ink"},
]
grouped = {}
for order in orders:
customer = order["customer"]
if customer not in grouped:
grouped[customer] = []
grouped[customer].append(order["item"])
print(grouped){'rafi': ['pen', 'ink'], 'ahmed': ['bag']}Die drei mittleren Zeilen sind die ganze Technik: Wenn der Key fehlt, lege zuerst eine leere Liste dort hin, dann häng an, ohne dich zu kümmern. Ohne das wirft die allererste Bestellung einen KeyError.
Dasselbe passt auf eine Zeile:
orders = [
{"customer": "rafi", "item": "pen"},
{"customer": "rafi", "item": "ink"},
]
grouped = {}
for order in orders:
grouped.setdefault(order["customer"], []).append(order["item"])
print(grouped){'rafi': ['pen', 'ink']}setdefault(key, []) sagt: „Gib mir den Wert dieses Keys, und wenn er keinen hat, lege diese leere Liste dort hin und gib mir dann die“ — und an alles, was zurückkommt, wird angehängt. Der Unterschied zu .get() ist wichtig: .get() steckt nichts in das Dictionary, setdefault schon.
Die Form mit drei Zeilen liest sich leichter, die mit einer Zeile schreibt sich schneller. Beide wirst du in echtem Code finden.
Noch tiefer
shop = {
"name": "Corner Store",
"staff": [
{"name": "rafi", "shifts": ["mon", "tue"]},
{"name": "dia", "shifts": ["wed"]},
],
}
print(shop["staff"][0]["shifts"][1])
print(len(shop["staff"]))
for person in shop["staff"]:
print(f"{person['name']}: {len(person['shifts'])} shift(s)")tue
2
rafi: 2 shift(s)
dia: 1 shift(s)shop["staff"][0]["shifts"][1] sind vier Schritte: Dictionary → Liste → Dictionary → Liste. Von links nach rechts gelesen ist es nicht kompliziert, nur lang.
Jenseits von drei Ebenen: stehenbleiben und nachdenken. Die Kette unterbrechen und dem mittleren Schritt einen Namen geben, macht den Code lesbar:
first_person = shop["staff"][0]
print(first_person["shifts"][1])copy() reicht bei verschachtelten Daten nicht
Kapitel zwölf hat gezeigt, dass b = a zwei Namen für dieselbe Liste schafft und copy() die Lösung ist. Bei verschachtelten Daten schützt copy() nur eine Ebene:
import copy
template = {"name": "", "items": []}
a = template.copy()
b = copy.deepcopy(template)
a["items"].append("pen")
b["items"].append("bag")
print(template)
print(a)
print(b){'name': '', 'items': ['pen']}
{'name': '', 'items': ['pen']}
{'name': '', 'items': ['bag']}a wurde mit copy() erzeugt — ein neues äußeres Dictionary, aber die innere Liste ist dieselbe Liste. Also hat a["items"].append("pen") auch das ursprüngliche template geändert.
b wurde mit deepcopy() erzeugt, das auch alles darin kopiert. Also verbreiten sich Änderungen an b nicht weiter.
copy ist ein Modul, also braucht es import copy vor der Benutzung — Module behandeln wir in Kapitel zweiundzwanzig ausführlich.
Wann brauchst dudeepcopy? Wenn du eine Kopie einer verschachtelten Struktur machst, um sie zu ändern, und das Original unangetastet lassen willst. Beachte, dassdeepcopylangsam ist und die meiste Zeit nicht gebraucht wird — der meiste Code baut neue Daten, statt vorhandene zu verändern.
Ein vollständiges Beispiel
orders.py:
# The shape data actually arrives in: a list of records
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},
]
print("Customer Item Qty Total")
grand_total = 0.0
for order in orders:
total = order["quantity"] * order["price"]
grand_total = grand_total + total
print(f"{order['customer']:<9} {order['item']:<6} {order['quantity']:>3} {total:>10.2f}")
print(f"{'':<20}{grand_total:>10.2f}")
print()
# Group the spend by customer: the key may not exist yet, so .get supplies a start
spend = {}
for order in orders:
customer = order["customer"]
spend[customer] = spend.get(customer, 0.0) + order["quantity"] * order["price"]
# Sort by amount by putting the amount first in a tuple
pairs = []
for customer, amount in spend.items():
pairs.append((amount, customer))
pairs.sort(reverse=True)
print("Spend per customer, highest first:")
for amount, customer in pairs:
print(f" {customer:<8} {amount:>8.2f}")
distinct = set()
for order in orders:
distinct.add(order["item"])
print()
print("Distinct items:", sorted(distinct))
print("Best customer :", pairs[0][1])Customer Item Qty Total
rafi pen 3 45.00
ahmed bag 1 850.00
rafi ink 2 240.00
dia pen 5 75.00
1210.00
Spend per customer, highest first:
ahmed 850.00
rafi 285.00
dia 75.00
Distinct items: ['bag', 'ink', 'pen']
Best customer : ahmedDieses kleine Programm benutzt alle vier Container der letzten Kapitel, jeden dort, wo er hingehört.
Eine Liste für die Bestellungen, weil die Anzahl unbekannt ist und die Reihenfolge zählt.
Dictionaries für jede Bestellung, weil jeder Teil einen Namen braucht; und für spend, weil es per Name akkumuliert.
Tupel für jedes Paar in pairs. Und hier steckt ein Trick: der Betrag wird zuerst ins Tupel gesetzt, weil Python Tupel nach ihrem ersten Wert sortiert. Ein einfaches .sort(reverse=True) ordnet also nach Betrag, und kein key= ist nötig.
Ein Set für distinct, weil die Frage „wie viele verschiedene“ war, und pen zweimal vorkommt.
Und spend.get(customer, 0.0) ist das Zählmuster aus Kapitel fünfzehn in anderem Gewand — es wird ein Betrag addiert statt um eins erhöht.
Wenn etwas schiefgeht
KeyError: 'discount' — aber in welchem Datensatz? Lauf mit for i, order in enumerate(orders): durch, damit i zur Hand ist, wenn es schiefgeht — wie im Beispiel oben. Oder stell print(order) in die Schleife; das zuletzt Ausgegebene ist das Schuldige.
TypeError: string indices must be integers Das bedeutet fast immer: Du dachtest, du hättest ein Dictionary, hast aber einen String. Wenn orders eine Liste von Namen ist statt eine Liste von Dictionaries, liefert order["customer"] genau das. Prüfe es mit print(type(order)).
TypeError: list indices must be integers or slices, not str Der umgekehrte Fehler — du hast eine Liste und fragst per Namen. Meistens fehlt ein [0]: orders[0]["customer"], nicht orders["customer"].
AttributeError: 'list' object has no attribute 'get' Dieselbe Sorte Fehler in anderer Form. Das Ding, worauf du .get() aufgerufen hast, ist eine Liste, kein Dictionary.
Ich habe eine Kopie geändert und das Original hat sich auch geändert copy() ist flach — innere Listen und Dictionaries bleiben geteilt. import copy und benutze copy.deepcopy(...).
Ein KeyError beim allerersten Durchlauf beim Gruppieren grouped[key].append(...) lief, bevor es den Key gab. Lege ihn mit if key not in grouped: und einer leeren Liste an, oder benutze setdefault.
Schritt 4 von 6 — Vorhersagen
Prüfe dein Verständnis
Zwei Ebenen tief. Was wird ausgegeben?
orders = [
{"customer": "rafi", "item": "pen"},
{"customer": "ahmed", "item": "bag"},
]
print(orders[1]["item"])- Apen
- Bbag
- Cahmed
- DEin `KeyError`
Ein Versuch zu gruppieren. Was passiert beim allerersten Durchlauf?
orders = [
{"customer": "rafi", "item": "pen"},
]
grouped = {}
for order in orders:
grouped[order["customer"]].append(order["item"])
print(grouped)- AEin `AttributeError` — ein Dictionary hat kein `append`
- B`KeyError: 'rafi'` — der Key existiert noch nicht, also gibt es nichts, an das man etwas anhängen könnte
- CEs gibt `{}` aus
- DEs funktioniert und gibt `{'rafi': ['pen']}` aus
Es wurde ein copy() gemacht und daran zwei Änderungen vorgenommen. Was ist mit dem Original geschehen?
import copy
template = {"name": "", "items": []}
a = template.copy()
a["name"] = "rafi"
a["items"].append("pen")
print(template)- A{'name': '', 'items': ['pen']}
- B{'name': '', 'items': []}
- C{'name': 'rafi', 'items': ['pen']}
- D{'name': 'rafi', 'items': []}
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 library.py mit einer Liste von mindestens sechs Büchern, jedes ein Dictionary mit Titel, Autor, Jahr und Anzahl der Exemplare.
Dann:
- Gib jedes Buch in ausgerichteten Spalten aus
- Berechne die Gesamtzahl der Exemplare und das Durchschnittsjahr
- Gruppiere nach Autor zu
{"author": ["title", "title"]}und gib aus, wie viele Bücher jeder hat - Zähle die verschiedenen Autoren mit einem Set
- Gib den Titel des Buchs mit den meisten Exemplaren aus
Dann brich es so, wie es echte Daten tun:
- Lösche den Key
"year"bei einem Buch komplett und führe das Programm aus. Welche Zeile hat es gestoppt, und hat die Meldung dir gesagt, welches Buch schuld war? - Benutz jetzt
.get("year", 0)in dieser Zeile und führe es nochmal aus. Was ist jetzt das Durchschnittsjahr, und stimmt das?
Diese letzte Frage ist die eigentliche Lektion. .get() hat den Fehler gestoppt, aber ein fehlendes Jahr als null zu behandeln und daraus einen Durchschnitt zu bilden, ist eine stillschweigend ermittelte falsche Antwort. Einen Fehler zu entfernen ist nicht dasselbe wie ein Problem zu lösen.
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