Python defaultdict: Dictionary-Operationen mit Standardwerten vereinfachen
Updated on
Jeder Python-Entwickler kennt dieses Problem: Sie schreiben eine saubere Schleife, um Elemente mit einem Dictionary zu gruppieren oder zu zählen, führen den Code aus, und ein KeyError bringt das gesamte Skript zum Absturz, weil ein Schlüssel noch nicht existiert. Die übliche Lösung ist, überall if key in dict-Prüfungen oder try/except KeyError-Blöcke einzustreuen. Ihre Logik zum Gruppieren von zehn Datenzeilen wächst plötzlich auf zwanzig Zeilen defensiven Boilerplate-Code an.
Bei großen Datenmengen wird es noch schlimmer. Wenn Sie Adjazenzlisten für Graphen erstellen, Log-Daten aggregieren oder Worthäufigkeiten über Millionen von Datensätzen zählen, summieren sich diese Schutzklauseln. Sie verlangsamen Ihre Entwicklungsarbeit, machen den Code schwerer zu überprüfen und führen zu subtilen Fehlern, wenn Sie eine Prüfung in einem Zweig vergessen.
Pythons collections.defaultdict beseitigt diese gesamte Kategorie von Problemen. Es ist eine Dictionary-Unterklasse, die eine Factory-Funktion aufruft, um fehlende Werte automatisch bereitzustellen. Kein KeyError mehr, keine Schutzklauseln mehr, kein Boilerplate mehr.
Was ist defaultdict?
Das defaultdict ist eine Unterklasse von Pythons eingebautem dict. Der wesentliche Unterschied: Wenn Sie auf einen nicht existierenden Schlüssel zugreifen, erstellt defaultdict ihn automatisch mit einem Standardwert, anstatt einen KeyError auszulösen.
from collections import defaultdict
# Reguläres dict löst KeyError aus
regular = {}
# regular['missing'] # KeyError: 'missing'
# defaultdict erstellt den Wert automatisch
dd = defaultdict(int)
dd['missing'] # Gibt 0 zurück, und 'missing' ist jetzt ein Schlüssel
print(dd) # defaultdict(<class 'int'>, {'missing': 0})Der Konstruktor nimmt eine Factory-Funktion als erstes Argument. Häufige Factories:
int-- gibt0zurücklist-- gibt[]zurückset-- gibtset()zurückstr-- gibt""zurücklambda: value-- gibt jeden benutzerdefinierten Standardwert zurück
defaultdict(int) -- Das Zählmuster
Die häufigste Verwendung. Jeder neue Schlüssel beginnt bei 0, sodass Sie sofort inkrementieren können.
from collections import defaultdict
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
# Ohne defaultdict
counts_regular = {}
for word in words:
if word in counts_regular:
counts_regular[word] += 1
else:
counts_regular[word] = 1
# Mit defaultdict(int) -- sauber und direkt
counts = defaultdict(int)
for word in words:
counts[word] += 1
print(dict(counts))
# {'apple': 3, 'banana': 2, 'cherry': 1}defaultdict(list) -- Das Gruppierungsmuster
Verwandte Elemente zusammenfassen. Jeder neue Schlüssel beginnt mit einer leeren Liste.
from collections import defaultdict
students = [
('Math', 'Alice'),
('Science', 'Bob'),
('Math', 'Charlie'),
('Science', 'Diana'),
('Math', 'Eve'),
('History', 'Frank'),
]
groups = defaultdict(list)
for subject, student in students:
groups[subject].append(student)
for subject, names in groups.items():
print(f"{subject}: {', '.join(names)}")
# Math: Alice, Charlie, Eve
# Science: Bob, Diana
# History: FrankDatensätze nach mehreren Feldern gruppieren
from collections import defaultdict
sales = [
{'region': 'East', 'product': 'Widget', 'amount': 100},
{'region': 'West', 'product': 'Gadget', 'amount': 200},
{'region': 'East', 'product': 'Widget', 'amount': 150},
{'region': 'West', 'product': 'Widget', 'amount': 300},
]
by_region_product = defaultdict(list)
for sale in sales:
key = (sale['region'], sale['product'])
by_region_product[key].append(sale['amount'])
for (region, product), amounts in by_region_product.items():
total = sum(amounts)
print(f"{region} - {product}: {amounts} (total: {total})")defaultdict(set) -- Eindeutige Gruppierung
Einzigartige Werte pro Schlüssel automatisch sammeln.
from collections import defaultdict
edges = [
('Alice', 'Bob'), ('Alice', 'Charlie'),
('Bob', 'Alice'), ('Bob', 'Diana'),
('Alice', 'Bob'), # duplicate
]
connections = defaultdict(set)
for person, friend in edges:
connections[person].add(friend)
for person, friends in connections.items():
print(f"{person} is connected to: {friends}")
# Alice is connected to: {'Bob', 'Charlie'}
# Bob is connected to: {'Alice', 'Diana'}defaultdict(lambda: value) -- Benutzerdefinierte Standardwerte
Wenn eingebaute Typen nicht passen, verwenden Sie ein lambda, um einen beliebigen Standardwert zurückzugeben.
from collections import defaultdict
# Standardwert 'N/A' für fehlende Einträge
status = defaultdict(lambda: 'N/A')
status['server1'] = 'running'
status['server2'] = 'stopped'
print(status['server3']) # N/A
# Standard-Startguthaben
accounts = defaultdict(lambda: 100.0)
accounts['alice'] += 50
accounts['bob'] -= 30
print(dict(accounts)) # {'alice': 150.0, 'bob': 70.0}Standard-Dictionary mit strukturierten Werten
from collections import defaultdict
def default_profile():
return {'score': 0, 'level': 1, 'items': []}
profiles = defaultdict(default_profile)
profiles['player1']['score'] += 100
profiles['player1']['items'].append('sword')
profiles['player2']['level'] = 5
print(profiles['player1'])
# {'score': 100, 'level': 1, 'items': ['sword']}
print(profiles['player3'])
# {'score': 0, 'level': 1, 'items': []}Verschachteltes defaultdict -- Baumstrukturen
Eines der mächtigsten Muster ist die rekursive Verwendung von defaultdict, um auto-vivifizierende Dictionaries zu erstellen.
from collections import defaultdict
def tree():
return defaultdict(tree)
taxonomy = tree()
taxonomy['Animal']['Mammal']['Dog'] = 'Canis lupus familiaris'
taxonomy['Animal']['Mammal']['Cat'] = 'Felis catus'
taxonomy['Animal']['Bird']['Eagle'] = 'Aquila chrysaetos'
taxonomy['Plant']['Tree']['Oak'] = 'Quercus'
print(taxonomy['Animal']['Mammal']['Dog']) # Canis lupus familiarisMehrstufige Aggregation
from collections import defaultdict
sales_data = [
(2025, 'Q1', 'Widget', 500),
(2025, 'Q1', 'Gadget', 300),
(2025, 'Q2', 'Widget', 700),
(2026, 'Q1', 'Widget', 600),
]
report = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
for year, quarter, product, amount in sales_data:
report[year][quarter][product] += amount
print(report[2025]['Q1']['Widget']) # 500
print(report[2026]['Q1']['Widget']) # 600defaultdict vs dict.setdefault() vs get() -- Vergleich
| Eigenschaft | defaultdict | dict.setdefault() | dict.get() |
|---|---|---|---|
| Import erforderlich | Ja (collections) | Nein | Nein |
| Erstellt Schlüssel automatisch | Ja | Ja | Nein |
| Ändert dict beim Zugriff | Ja | Ja | Nein |
| Benutzerdefinierter Standard pro Aufruf | Nein (globale Factory) | Ja | Ja |
| Leistung (wiederholt) | Am schnellsten | Langsamer (Methodenaufruf-Overhead) | Am schnellsten (keine Mutation) |
| Am besten für | Wiederholte Akkumulation | Einmalige Standards | Nur-Lese-Fallback |
Wann was verwenden:
defaultdict: Werte über viele Iterationen aufbauen (Zählen, Gruppieren)dict.setdefault(): gelegentlich einen Standard für einen bestimmten Schlüssel benötigendict.get(): einen Wert mit Fallback lesen, ohne das Dictionary zu ändern
defaultdict zurück in reguläres dict konvertieren
from collections import defaultdict
import json
def defaultdict_to_dict(d):
"""Recursively convert defaultdict to regular dict."""
if isinstance(d, defaultdict):
d = {k: defaultdict_to_dict(v) for k, v in d.items()}
return d
nested = defaultdict(lambda: defaultdict(int))
nested['x']['y'] = 10
nested['a']['b'] = 20
regular = defaultdict_to_dict(nested)
print(json.dumps(regular)) # {"x": {"y": 10}, "a": {"b": 20}}Sie können die Standard-Factory auch deaktivieren, indem Sie sie auf None setzen:
dd = defaultdict(int)
dd['a'] += 1
dd.default_factory = None
# dd['missing'] # Löst jetzt KeyError ausPraktische Beispiele
Adjazenzliste für Graphen
from collections import defaultdict, deque
edges = [('A', 'B'), ('A', 'C'), ('B', 'D'), ('C', 'D'), ('D', 'E')]
graph = defaultdict(list)
for src, dst in edges:
graph[src].append(dst)
graph[dst].append(src) # undirected graph
def bfs(graph, start):
visited = set()
queue = deque([start])
order = []
while queue:
node = queue.popleft()
if node not in visited:
visited.add(node)
order.append(node)
queue.extend(graph[node])
return order
print(bfs(graph, 'A')) # ['A', 'B', 'C', 'D', 'E']Invertierter Index für Textsuche
from collections import defaultdict
documents = {
'doc1': 'python is a great programming language',
'doc2': 'data science uses python extensively',
'doc3': 'machine learning with python and data',
}
index = defaultdict(set)
for doc_id, text in documents.items():
for word in text.split():
index[word.lower()].add(doc_id)
def search(query):
return index.get(query.lower(), set())
print(search('python')) # {'doc1', 'doc2', 'doc3'}
print(search('data')) # {'doc2', 'doc3'}Gruppierte Daten mit PyGWalker visualisieren
Nach dem Gruppieren und Aggregieren von Daten mit defaultdict möchten Sie die Ergebnisse oft visualisieren. PyGWalker (opens in a new tab) verwandelt Ihren pandas DataFrame in eine interaktive Visualisierungsoberfläche direkt in Jupyter:
from collections import defaultdict
import pandas as pd
import pygwalker as pyg
sales = [
('Electronics', 'Laptop', 1200),
('Electronics', 'Phone', 800),
('Clothing', 'Shirt', 45),
('Clothing', 'Jacket', 120),
]
totals = defaultdict(lambda: defaultdict(int))
for category, product, amount in sales:
totals[category][product] += amount
rows = []
for category, products in totals.items():
for product, total in products.items():
rows.append({'category': category, 'product': product, 'total': total})
df = pd.DataFrame(rows)
walker = pyg.walk(df)FAQ
Was ist defaultdict in Python?
defaultdict ist eine Dictionary-Unterklasse in collections, die einen Standardwert für fehlende Schlüssel bereitstellt. Anstatt einen KeyError auszulösen, ruft es eine Factory-Funktion (wie int, list oder set) auf, um automatisch einen Standardwert zu erstellen und zu speichern.
Was ist der Unterschied zwischen dict und defaultdict?
Der einzige funktionale Unterschied ist, wie sie fehlende Schlüssel behandeln. Ein reguläres dict löst einen KeyError aus. Ein defaultdict ruft seine default_factory-Funktion auf, um einen Standardwert zu erstellen. In allen anderen Aspekten verhalten sie sich identisch.
Wann sollte ich defaultdict(list) vs defaultdict(set) verwenden?
Verwenden Sie defaultdict(list), wenn Sie Elemente gruppieren und Duplikate sowie die Einfügereihenfolge beibehalten möchten. Verwenden Sie defaultdict(set), wenn Sie nur eindeutige Elemente pro Schlüssel sammeln möchten.
Kann ich ein defaultdict in JSON serialisieren?
Ja, aber für verschachtelte defaultdict-Objekte konvertieren Sie diese zuerst in reguläre dict-Objekte mit einer rekursiven Konvertierungsfunktion. Sie können auch default_factory = None setzen, um eine versehentliche Schlüsselerstellung vor der Serialisierung zu verhindern.
Wie erstelle ich ein verschachteltes defaultdict?
Definieren Sie eine rekursive Factory-Funktion: def tree(): return defaultdict(tree). Für einfachere zweistufige Verschachtelung verwenden Sie defaultdict(lambda: defaultdict(int)).
Fazit
Pythons collections.defaultdict ist eines der praktischsten Werkzeuge in der Standardbibliothek. Es wandelt ausführliche, fehleranfällige Dictionary-Akkumulationsmuster in saubere Einzeiler um. Verwenden Sie defaultdict(int) zum Zählen, defaultdict(list) zum Gruppieren, defaultdict(set) für eindeutige Sammlungen und verschachteltes defaultdict für hierarchische Daten.
Die wichtigste Erkenntnis: Wenn Sie feststellen, dass Sie vor jeder Dictionary-Operation if key not in dict schreiben, ersetzen Sie dieses Dictionary durch ein defaultdict. Ihr Code wird kürzer, schneller und wesentlich wartbarer.