defaultdict : Die Evolution des bedingungslosen Wörterbuchs

In Python gibt es zahlreiche externe Bibliotheken, aber allein mit dem Verständnis der Standardbibliothek können Sie bereits leistungsstarken Code für den praktischen Einsatz erstellen. In diesem Artikel werden wir uns eingehend mit collections.defaultdict beschäftigen.

Durch diesen Artikel werden Sie über die einfache Konzeptvorstellung hinaus verstehen, wann, warum und wie Sie defaultdict effektiv einsetzen können.

Wenn Sie an der ersten Klasse der collections-Serie, der Counter-Klasse interessiert sind, empfehle ich Ihnen, den vorherigen Artikel zu lesen: Python Standardbibliothek ① - collections.Counter


1. Grundkonzept: Was ist defaultdict?

Tux mit defaultdicts

defaultdict ist eine spezielle Unterklasse des Dictionaries (dict), die im Python-Standardbibliotheksmodul collections enthalten ist. Bei einem normalen Dictionary tritt ein KeyError auf, wenn auf einen nicht vorhandenen Schlüssel zugegriffen wird, aber defaultdict ermöglicht es, eine Funktion zum automatischen Erstellen von Standardwerten (Factory Function) anzugeben, dadurch wird der Code viel sauberer und Fehler werden vermieden.


2. Grundlegende Verwendung

from collections import defaultdict

d = defaultdict(int)
d['apple'] += 1
print(d)  # defaultdict(<class 'int'>, {'apple': 1})

Hier gibt int() 0 als Standardwert zurück. Wenn auf den nicht existierenden Schlüssel 'apple' zugegriffen wird, wird automatisch ohne KeyError eine 0 erstellt, und danach wird +1 angewendet.


3. Verschiedene Beispiele für Standardwerte

from collections import defaultdict

# Standardwert: 0 (int)
counter = defaultdict(int)
counter['a'] += 1
print(counter)  # defaultdict(<class 'int'>, {'a': 1})

# Standardwert: leere Liste
group = defaultdict(list)
group['fruit'].append('apple')
group['fruit'].append('banana')
print(group)  # defaultdict(<class 'list'>, {'fruit': ['apple', 'banana']})

# Standardwert: leere Menge
unique_tags = defaultdict(set)
unique_tags['tags'].add('python')
unique_tags['tags'].add('coding')
print(unique_tags)  # defaultdict(<class 'set'>, {'tags': {'python', 'coding'}})

# Standardwert: benutzerdefinierter Anfangswert
fixed = defaultdict(lambda: 100)
print(fixed['unknown'])  # 100

4. Praktische Beispiele

1. Zählen der Wortfrequenzen

words = ['apple', 'banana', 'apple', 'orange', 'banana']
counter = defaultdict(int)

for word in words:
    counter[word] += 1

print(counter)
# defaultdict(<class 'int'>, {'apple': 2, 'banana': 2, 'orange': 1})

👉 Counter vs defaultdict
Das Zählen von Wortfrequenzen ist spezialisierter auf collections.Counter(), daher ist es ratsam, Counter bei statistischen oder Rankinganalysen zu verwenden. Bei einfachen Summierungen wie dem kumulativen Zählen kann jedoch auch defaultdict(int) ausreichend klar verwendet werden.

2. Protokolle nach Gruppen ordnen

logs = [
    ('2024-01-01', 'INFO'),
    ('2024-01-01', 'ERROR'),
    ('2024-01-02', 'DEBUG'),
]

grouped = defaultdict(list)
for date, level in logs:
    grouped[date].append(level)

print(grouped)
# defaultdict(<class 'list'>, {'2024-01-01': ['INFO', 'ERROR'], '2024-01-02': ['DEBUG']})

3. Aufbereitung von doppelten Tags

entries = [
    ('post1', 'python'),
    ('post1', 'coding'),
    ('post1', 'python'),
]

tags = defaultdict(set)
for post, tag in entries:
    tags[post].add(tag)

print(tags)
# defaultdict(<class 'set'>, {'post1': {'python', 'coding'}})

5. Hinweise

  • defaultdict speichert intern den Standardwert-Erzeuger, sodass beim repr() ein anderes Format als bei einem normalen dict resultieren kann.
  • Dies könnte bei der JSON-Serialisierung zu Problemen führen. Es ist sicherer, dies mit dict(d) zu konvertieren und zu verarbeiten.
  • Der Standardwert wird nur bei Zugriff über [] erzeugt. Bei Zugriff über get() wird er nicht erzeugt.
from collections import defaultdict

d = defaultdict(list)
print(d.get('missing'))  # None
print(d['missing'])      # []

6. Wann ist es nützlich? – Drei entscheidende Vorteile von defaultdict

defaultdict verbessert dict + Bedingung-Muster erheblich in Bezug auf Lesbarkeit, Wartbarkeit und Sicherheit. Besonders in den folgenden drei Situationen werden Sie denken: 'Ja, in diesem Fall sollte ich unbedingt defaultdict verwenden!'

6-1. Aggregationscode ohne Bedingungen zählen/kumulieren

from collections import defaultdict

# Normales dict
counts = {}
for item in items:
    if item not in counts:
        counts[item] = 0
    counts[item] += 1

# defaultdict
counts = defaultdict(int)
for item in items:
    counts[item] += 1

✔ Die Bedingung entfällt, was den Code vereinfacht und das Fehlerrisiko verringert.
✔ Besonders geeignet für die Verarbeitung von großen Datenmengen, wie z.B. Protokollanalysen und Wortzählungen.

6-2. Ersetzen von setdefault beim kumulierten Arbeiten mit Listen/Mengen

from collections import defaultdict

# Normales dict
posts = {}
for tag, post in data:
    if tag not in posts:
        posts[tag] = []
    posts[tag].append(post)

# defaultdict
posts = defaultdict(list)
for tag, post in data:
    posts[tag].append(post)

✔ Ist viel intuitiver als setdefault() und sieht auch in Schleifen ordentlich aus.
Optimierte Struktur zur Gruppierung von Daten.

6-3. Automatisierung der Initialisierung beim Erstellen von geschachtelten Dictionaries

# Normales Dictionary
matrix = {}
if 'x' not in matrix:
    matrix['x'] = {}
matrix['x']['y'] = 10

# Geschachteltes defaultdict
matrix = defaultdict(lambda: defaultdict(int))  # Bei Nichtexistenz wird automatisch ein defaultdict(int) erzeugt
matrix['x']['y'] += 10

✔ Ermöglicht es, verschachtelte Datenstrukturen einfach zu erstellen, was für multidimensionale Dictionary-Arbeiten sehr vorteilhaft ist.
✔ Zeigt starke Vorteile beim Daten-Mining, Parsing und Speichern von Baustrukturen.

lambda: defaultdict(int) ist so strukturiert, dass es bei Nichtexistenz der Schlüssel automatisch defaultdict(int) zurückgibt, wodurch das Dictionary automatisch geschachtelt erzeugt wird.


7. Zusammenfassung

collections.defaultdict mag auf den ersten Blick wie eine einfache Erweiterung von dict erscheinen, aber je mehr Sie es verwenden, desto mehr spüren Sie, dass es ein strukturelles Werkzeug ist, das Ihren Code klarer und sicherer macht.

  • Sie können Dictionaries verwenden, ohne sich um KeyError sorgen zu müssen.
  • Es ist möglich, Daten ohne Bedingungen zu gruppieren und zu akkumulieren.
  • Verschachtelte Dictionaries können auf intuitive Weise erstellt werden.
# Beispiel für eine zuverlässige und einfache Verarbeitung mit defaultdict
salaries = defaultdict(int)
for dept, amount in records:
    salaries[dept] += amount

Wenn Sie mit einer Zeile Code Fehlervermeidung, Lesbarkeit und Wartbarkeit erreichen können,
dann ist defaultdict nicht nur eine praktische Funktion, sondern ein zentraler Bestandteil eines Python-gerechten Denkansatzes.

Das nächste Thema wird pathlib sein.
Es handelt sich um einen modernen Ansatz, um den Umgang mit Dateien/Verzeichnissen objektorientiert zu gestalten, der sowohl für Anfänger als auch für Fortgeschrittene nützlich ist. Entwickler, die mit os.path vertraut sind, werden denken: "Wow! Das ist viel einfacher als os.path!" Freuen Sie sich auf die nächste Folge.