Overview
The collections module extends Python’s built-in types with specialized containers. Each type solves a specific pattern more cleanly than a plain dict or list. This card maps each collection to its primary use case and shows the API surface that matters in daily work. For type annotation patterns, see python-typing.
from collections import Counter, defaultdict, namedtuple, deque, ChainMap, OrderedDictCounter
Count hashable objects. Built-in most_common, arithmetic, and set operations.
| Pattern | Code | Notes |
|---|---|---|
| Count from iterable | Counter("aababc") | {'a': 3, 'b': 2, 'c': 1} |
| Count from dict | Counter({'a': 3, 'b': 2}) | Accepts any mapping. |
| Most common N | c.most_common(3) | Returns list of (element, count) tuples, descending. |
| Add counts | c + Counter("ab") | Returns new Counter; negative counts dropped. |
| Subtract counts | c.subtract("aab") | In-place; keeps zero and negative counts (unlike -). |
| Elements | list(c.elements()) | Expand back to iterable; repeats each element count times. |
| Missing key | c["missing"] | Returns 0, not KeyError. |
words = "the quick brown fox jumps over the lazy dog the fox".split()
freq = Counter(words)
print(freq.most_common(3)) # [('the', 3), ('fox', 2), ...]Use Counter for word frequency, histogram bins, and vote tallies.
defaultdict
Dict that calls a factory for missing keys instead of raising KeyError.
| Pattern | Code | Notes |
|---|---|---|
| List of values per key | defaultdict(list) | d[key].append(v) without checking membership. |
| Count with int | defaultdict(int) | d[key] += 1 without initialization. |
| Nested dict | defaultdict(dict) | d[outer][inner] = v in one step. |
| Custom factory | defaultdict(lambda: "N/A") | Any zero-arg callable. |
| Access without inserting | d.get(key) | d[key] inserts the default; .get() does not. |
# Group words by first letter
from collections import defaultdict
groups = defaultdict(list)
for word in ["apple", "avocado", "banana", "blueberry", "cherry"]:
groups[word[0]].append(word)
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}Prefer defaultdict(list) over setdefault in loops; it reads more clearly.
namedtuple and dataclass
namedtuple creates lightweight immutable record types. Use dataclass when you need mutability or methods.
| API | Code | Notes |
|---|---|---|
| Define | Point = namedtuple("Point", ["x", "y"]) | Also accepts space-separated string "x y". |
| Instantiate | p = Point(1, 2) | Positional or keyword arguments. |
| Access | p.x, p[0] | Both attribute and index access work. |
| As dict | p._asdict() | Returns dict; useful for serialization. |
| Replace a field | p._replace(x=10) | Returns new instance; original unchanged. |
| Defaults | Point = namedtuple("Point", ["x", "y"], defaults=[0, 0]) | Python 3.6.1+. |
| Typed variant | from typing import NamedTuple | Allows per-field type hints. |
from typing import NamedTuple
class Config(NamedTuple):
host: str
port: int = 5432
ssl: bool = True
cfg = Config(host="localhost")deque
Double-ended queue; O(1) append and pop from both ends. Use instead of a list when you need a sliding window or FIFO queue.
| Operation | Code | Notes |
|---|---|---|
| Create bounded | deque(maxlen=100) | Old items auto-dropped when full. |
| Append right | d.append(v) | Same as list. |
| Append left | d.appendleft(v) | O(1); list insert(0, v) is O(n). |
| Pop right | d.pop() | Raises IndexError if empty. |
| Pop left | d.popleft() | O(1) FIFO dequeue. |
| Rotate | d.rotate(n) | Right-rotate by n; negative rotates left. |
| Extend both ends | d.extend(iterable), d.extendleft(iterable) | extendleft reverses insertion order. |
# Sliding window of last 5 log lines
log_tail = deque(maxlen=5)
for line in read_log():
log_tail.append(line)ChainMap
Treats multiple dicts as a single logical view. Lookups search dicts left to right; writes go to the first dict.
| Pattern | Code | Notes |
|---|---|---|
| Create | ChainMap(user_cfg, project_cfg, defaults) | First dict has highest priority. |
| Add a layer | cm.new_child({"key": "val"}) | Returns new ChainMap; original unchanged. |
| Remove top layer | cm.parents | View without the first map. |
| Write through | cm["key"] = v | Writes to the first dict only. |
| All maps | cm.maps | List of underlying dicts in priority order. |
import os
from collections import ChainMap
# Config priority: CLI args > env vars > defaults
defaults = {"debug": False, "port": 8000, "host": "0.0.0.0"}
env = {k.lower(): v for k, v in os.environ.items() if k in ("PORT", "HOST", "DEBUG")}
cli = {"port": 9000} # from argparse
config = ChainMap(cli, env, defaults)
print(config["port"]) # 9000 (cli wins)Common gotchas
Countersubtraction with-drops zero and negative counts silently. Use.subtract()when you need to keep those counts for further arithmetic.defaultdictinserts the default value on key access (d[missing]). Iteratingd.keys()after accidental reads produces unexpected entries. Used.get(key)when you only want to check membership.namedtuplefields are positional. Renaming a field in the definition breaks any code that uses positional construction. PreferNamedTuplewith keyword defaults for public APIs.dequewithmaxlendoes not raise on overflow; it silently drops the oldest item. If dropping data silently is wrong for your use case, checklen(d) == d.maxlenbefore appending.ChainMapwrites go to the first dict. If the first dict is empty and you read then write the same key, the value is written to the empty dict and the original lower-priority dict is unchanged.OrderedDictis rarely needed in Python 3.7+ because plaindictpreserves insertion order. UseOrderedDictonly when you needmove_to_endor equality that respects order.