Python collections-Modul
Das Python-collections-Modul erklärt: Counter, defaultdict, namedtuple, deque, OrderedDict und ChainMap mit praktischen Beispielen.
Das eingebaute Python-Modul collections bietet spezialisierte Container-Typen, die die standardmäßigen Typen list, dict und tuple erweitern oder ersetzen. Jeder Typ löst ein bestimmtes Problem, das andernfalls mehrere zusätzliche Zeilen manuellen Verwaltungsaufwands erfordern würde.
Dieses Kapitel behandelt alle sechs häufig verwendeten Typen: Counter, defaultdict, namedtuple, deque, OrderedDict und ChainMap. Für jeden Typ wird gezeigt, welches Problem er löst, wie man ihn erstellt und verwendet und worauf man achten sollte.
Es ist keine Installation erforderlich — collections ist in jeder Python 3-Installation enthalten:
from collections import Counter, defaultdict, namedtuple, deque, OrderedDict, ChainMapCounter
Counter ist eine dict-Unterklasse, die zum Zählen hashbarer Objekte entwickelt wurde. Man übergibt ihr ein Iterable (oder einen string, oder Schlüsselwortargumente) und erhält ein wörterbuchähnliches Objekt zurück, bei dem die Schlüssel Elemente und die Werte deren Anzahl sind.
Counter erstellen
from collections import Counter
# From a list
word_list = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
c = Counter(word_list)
print(c)
# Output: Counter({'apple': 3, 'banana': 2, 'cherry': 1})Fehlende Schlüssel geben 0 zurück, anstatt einen KeyError auszulösen:
print(c['apple']) # 3
print(c['mango']) # 0 — no KeyErrorHäufigste Elemente
most_common(n) gibt die n Elemente mit der höchsten Anzahl als Liste von (element, count)-Tupeln zurück, sortiert von am häufigsten bis am seltensten:
print(c.most_common(2))
# Output: [('apple', 3), ('banana', 2)]Lässt man n weg, werden alle Elemente nach Häufigkeit sortiert ausgegeben.
Counter-Arithmetik
Counter unterstützen Addition, Subtraktion, Schnittmenge und Vereinigung:
a = Counter(['a', 'a', 'b']) # Counter({'a': 2, 'b': 1})
b = Counter(['a', 'b', 'b', 'c']) # Counter({'b': 2, 'a': 1, 'c': 1})
print(a + b) # Counter({'a': 3, 'b': 3, 'c': 1})
print(a - b) # Counter({'a': 1}) — only positive counts kept
print(a & b) # Counter({'a': 1, 'b': 1}) — minimum of each count
print(a | b) # Counter({'a': 2, 'b': 2, 'c': 1}) — maximum of each countWann Counter verwenden: Stimmen auszählen, Worthäufigkeiten, Zeichenanzahl, Histogramm erstellen.
Counter-Tücke: Subtraktion behält nur positive Werte
a - b verwirft stillschweigend Elemente, bei denen das Ergebnis null oder negativ wäre. Wenn man negative Zählwerte beibehalten möchte, verwendet man stattdessen subtract():
a = Counter({'x': 2})
b = Counter({'x': 5})
a.subtract(b)
print(a) # Counter({'x': -3}) — negative count preserveddefaultdict
defaultdict ist eine dict-Unterklasse, die eine Fabrikfunktion aufruft, um einen Standardwert bereitzustellen, wenn man einen noch nicht vorhandenen Schlüssel nachschlägt. Dadurch entfällt die Notwendigkeit von if key not in d:-Wächterklauseln.
defaultdict erstellen
Die Fabrik wird als erstes Argument übergeben:
from collections import defaultdict
dd = defaultdict(int) # default value: int() == 0
words = ['cat', 'dog', 'cat', 'bird', 'dog', 'cat']
for word in words:
dd[word] += 1 # no KeyError on first access
print(dict(dd))
# Output: {'cat': 3, 'dog': 2, 'bird': 1}Ohne defaultdict würde man dd[word] = dd.get(word, 0) + 1 oder einen Counter benötigen.
Elemente mit list als Fabrik gruppieren
groups = defaultdict(list)
data = [('fruit', 'apple'), ('veggie', 'carrot'), ('fruit', 'banana'), ('veggie', 'broccoli')]
for category, item in data:
groups[category].append(item)
print(dict(groups))
# Output: {'fruit': ['apple', 'banana'], 'veggie': ['carrot', 'broccoli']}Häufige Fabrikfunktionen
| Fabrik | Standardwert | Typischer Einsatz |
|---|---|---|
int | 0 | Zählen |
float | 0.0 | Summen akkumulieren |
list | [] | Elemente gruppieren |
set | set() | Eindeutige Werte sammeln |
str | '' | Strings aufbauen |
dict | {} | Verschachtelte Zuordnungen |
Man kann auch ein nullstelliges Lambda für einen benutzerdefinierten Standard übergeben: defaultdict(lambda: 'N/A').
defaultdict-Tücke: Zugriff auf einen Schlüssel erstellt ihn
Anders als dict.get() fügt ein einfaches dd[key]-Nachschlagen bei einem fehlenden Schlüssel diesen Schlüssel mit dem Standardwert ein. Das kann beim Iterieren oder Prüfen der Zugehörigkeit überraschen:
dd = defaultdict(int)
print('foo' in dd) # False — key does not exist yet
_ = dd['foo'] # access inserts the key
print('foo' in dd) # True — key was silently createdVerwendet man dd.get('foo') oder 'foo' in dd, wenn man ohne Nebeneffekte prüfen möchte.
Wann defaultdict verwenden: Daten gruppieren, Adjazenzlisten für Graphen erstellen, jedes Muster, bei dem man initialisiert und dann aktualisiert.
namedtuple
namedtuple erstellt eine neue Klasse, deren Instanzen wie reguläre Tupel sind, aber benannte Felder besitzen. Das Ergebnis ist unveränderlich, speichereffizient (kein __dict__ pro Instanz) und selbstdokumentierend.
namedtuple erstellen
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 7)
print(p) # Point(x=3, y=7)
print(p.x) # 3
print(p.y) # 7
print(p[0]) # 3 — index access still worksDas erste Argument an namedtuple() ist der Typname (wird in repr verwendet). Das zweite Argument ist eine Liste von Feldnamen (oder ein durch Leerzeichen/Komma getrennter string: 'x y').
Praktisches Beispiel
Employee = namedtuple('Employee', ['name', 'department', 'salary'])
emp = Employee('Alice', 'Engineering', 95000)
print(emp.name, emp.department, emp.salary)
# Output: Alice Engineering 95000Benannter Zugriff (emp.name) ist viel klarer als positionaler Zugriff (row[0]), wenn man Daten aus CSV-Dateien oder Datenbankzeilen liest.
Nützliche namedtuple-Methoden
# Convert to an ordered dictionary
print(p._asdict()) # {'x': 3, 'y': 7}
# Create a modified copy (namedtuples are immutable)
p2 = p._replace(x=10)
print(p2) # Point(x=10, y=7)
print(p) # Point(x=3, y=7) — original unchangednamedtuple vs. dataclass
Python 3.7 hat dataclasses.dataclass als Alternative eingeführt. Man wählt namedtuple, wenn man Unveränderlichkeit und volle Tupel-Kompatibilität (Entpacken, Indizieren, Hashing) möchte. Man wählt dataclass, wenn man veränderliche Felder, Standard-Fabriken oder Methoden benötigt.
Wann namedtuple verwenden: Datensätze repräsentieren (Datenbankzeilen, CSV-Zeilen, Koordinatenpaare, RGB-Farben), bei denen Unveränderlichkeit und geringer Speicherverbrauch wichtig sind.
deque
deque (doppelseitige Warteschlange, ausgesprochen „deck") ist eine Sequenz, die für O(1)-Anfüge- und Entfernoperationen an beiden Enden optimiert ist. Eine reguläre list erreicht O(1) bei append und O(n) bei insert(0, …); deque erreicht O(1) an beiden Enden.
deque erstellen
from collections import deque
d = deque([1, 2, 3])
print(d) # deque([1, 2, 3])Anfügen und Entfernen
d.append(4) # add to right
d.appendleft(0) # add to left
print(d) # deque([0, 1, 2, 3, 4])
d.pop() # remove from right → 4
d.popleft() # remove from left → 0
print(d) # deque([1, 2, 3])Eine deque rotieren
rotate(n) verschiebt Elemente um n Positionen nach rechts (negativ = links):
d = deque([1, 2, 3])
d.rotate(1)
print(d) # deque([3, 1, 2])
d.rotate(-1)
print(d) # deque([1, 2, 3])Begrenzte deque (Schiebefenster / FIFO-Puffer)
Das Setzen von maxlen begrenzt die Größe der deque. Wenn neue Elemente über die Grenze hinaus hinzugefügt werden, fallen Elemente automatisch vom gegenüberliegenden Ende heraus — ideal, um die letzten N Ereignisse zu speichern:
buffer = deque(maxlen=3)
for i in range(5):
buffer.append(i)
print(buffer) # deque([2, 3, 4], maxlen=3)deque-Tücke: langsamer O(n)-Zufallszugriff
deque unterstützt keinen effizienten Zufallszugriff. d[500] ist O(n), nicht O(1) wie bei einer list. Wenn man häufig nach Position indiziert, sollte man eine list verwenden. deque nur verwenden, wenn man schnelle Hinzufüge- und Entfernoperationen an beiden Enden benötigt.
Wann deque verwenden: Warteschlangen und Stapel implementieren, Schiebefenster-Algorithmen, Breitensuche, die letzten N Log-Einträge speichern.
OrderedDict
Seit Python 3.7 behält ein reguläres dict die Einfügereihenfolge bei. Warum also OrderedDict verwenden?
Zwei Gründe bleiben relevant:
move_to_end()— ermöglicht das effiziente Umordnen von Schlüsseln an den Anfang oder das Ende.- Gleichheit — zwei
OrderedDict-Instanzen mit denselben Schlüsseln in unterschiedlicher Einfügereihenfolge gelten als nicht gleich, im Gegensatz zu regulären dicts.
OrderedDict erstellen und umordnen
from collections import OrderedDict
od = OrderedDict()
od['one'] = 1
od['two'] = 2
od['three'] = 3
print(list(od.keys())) # ['one', 'two', 'three']
od.move_to_end('one') # move 'one' to the end
print(list(od.keys())) # ['two', 'three', 'one']
od.move_to_end('three', last=False) # move 'three' to the front
print(list(od.keys())) # ['three', 'two', 'one']Reihenfolgesensitive Gleichheit
od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])
print(od1 == od2) # False — different order
d1 = {'a': 1, 'b': 2}
d2 = {'b': 2, 'a': 1}
print(d1 == d2) # True — regular dicts ignore orderWann OrderedDict verwenden: LRU-Cache-Implementierungen (zuletzt verwendeten Schlüssel ans Ende verschieben), jeden Algorithmus, bei dem die Einfügereihenfolge Teil der Gleichheit sein muss.
ChainMap
ChainMap fasst mehrere Wörterbücher zu einer einzigen logischen Ansicht zusammen. Nachschläge durchsuchen die Maps der Reihe nach; Schreib- und Löschoperationen betreffen immer nur die erste Map.
Grundlegende Verwendung
from collections import ChainMap
defaults = {'color': 'blue', 'size': 'medium', 'theme': 'light'}
overrides = {'color': 'red', 'size': 'large'}
combined = ChainMap(overrides, defaults)
print(combined['color']) # 'red' — found in overrides first
print(combined['theme']) # 'light' — not in overrides, falls back to defaultsSchreiboperationen gehen nur an die erste Map:
combined['font'] = 'serif'
print(overrides) # {'color': 'red', 'size': 'large', 'font': 'serif'}
print(defaults) # {'color': 'blue', 'size': 'medium', 'theme': 'light'} — unchangedVariablen-Scopes mit new_child() simulieren
base = ChainMap({'x': 1})
child = base.new_child({'x': 99, 'y': 2})
print(child['x']) # 99 — child scope shadows parent
print(child['y']) # 2
print(child.parents['x']) # 1 — access parent scope directlynew_child() gibt eine neue ChainMap mit einem leeren dict am Anfang zurück, was der internen Modellierung von Pythons eigenen Scoping-Regeln (lokal → umschließend → global → eingebaut) entspricht.
Wann ChainMap verwenden: Konfigurationsschichtung (Benutzer-Überschreibungen → Projektstandards → globale Standards), Umgebungen mit Gültigkeitsbereich implementieren, CLI-Argumente mit Umgebungsvariablen und Konfigurationsdateien kombinieren.
Den richtigen Typ wählen
| Was man braucht… | Verwende |
|---|---|
| Häufigkeit von Elementen zählen | Counter |
KeyError mit einem Standardwert vermeiden | defaultdict |
| Einen Datensatz mit benannten Feldern repräsentieren | namedtuple |
| Schnelles Anfügen/Entfernen an beiden Enden oder ein begrenzter Puffer | deque |
Reihenfolgesensitive dict-Gleichheit oder move_to_end() | OrderedDict |
| Mehrere dicts ohne Kopieren zu einer Ansicht zusammenführen | ChainMap |
Mehr zu den Basistypen, die diese Typen erweitern, findet man unter Python Dictionaries, Python Lists und Python Tuples. Für iteratorbasierte Hilfsfunktionen in der Standardbibliothek siehe das Python itertools-Modul.