collections: deque, Counter, defaultdict & namedtuple

Python’s built-in collections module provides specialized container data structures designed for specific algorithmic workloads. Beyond basic lists and dicts, using deque, Counter, defaultdict, ChainMap, and namedtuple improves algorithmic performance, eliminates key management boilerplate, and reduces memory overhead.

This chapter details collections.deque block array mechanics, Counter multi-set mathematics, defaultdict missing key hooks (__missing__), and ChainMap lookup chains.


1. collections.deque Architecture (Block Array Mechanics)

A collections.deque (double-ended queue) is not a traditional single-node doubly linked list. In CPython, it is implemented as a doubly linked list of fixed-size memory blocks (each block holding 64 element pointers):

#define BLOCKLEN 64

typedef struct _block {
    struct _block *leftlink;
    struct _block *rightlink;
    PyObject *data[BLOCKLEN];
} block;

typedef struct {
    PyObject_VAR_HEAD
    block *leftblock;
    block *rightblock;
    size_t leftindex;
    size_t rightindex;
    // ...
} dequeobject;
CPython deque Memory Block Architecture:

[ Left Block (64 Slots) ] <---> [ Middle Block (64 Slots) ] <---> [ Right Block (64 Slots) ]
  ^                                                                 ^
  |                                                                 |
leftindex (popleft/appendleft)                                   rightindex (pop/append)

Performance Invariants:

  • Head & Tail Operations (append, appendleft, pop, popleft): $O(1)$ constant time. Pushing or popping at either end modifies block indices without shifting surrounding elements.
  • Random Access (deque[i]): $O(N)$ linear time. Accessing middle elements requires traversing block links from the nearest end.

2. defaultdict & The __missing__ Protocol

defaultdict inherits from dict and overrides the __missing__(key) dunder method:

When a key is missing during bracket access (d[key]):

  1. dict.__getitem__ fails to find the key.
  2. It delegates to __missing__(key).
  3. defaultdict calls its default_factory callable (e.g. list, int, set), inserts the returned value into the dict, and returns it.

Gotcha: d.get(missing_key) does NOT invoke __missing__() and will return None or the specified default without inserting the key into the dictionary!


3. Counter Multi-set Mathematics

Counter is a dict subclass designed for counting hashable objects. It supports multiset mathematical operations:

from collections import Counter

inventory_a = Counter(apples=5, bananas=2)
inventory_b = Counter(apples=3, bananas=4, oranges=1)

# Multiset Addition & Subtraction
total = inventory_a + inventory_b       # Counter({'apples': 8, 'bananas': 6, 'oranges': 1})
difference = inventory_a - inventory_b  # Counter({'apples': 2}) (Strips zero and negative counts!)
intersection = inventory_a & inventory_b # Counter({'apples': 3, 'bananas': 2}) (Takes min count)

4. ChainMap Scope Lookup & namedtuple Memory

  • ChainMap: Groups multiple dictionaries into a single updateable view without copying data. Lookups scan dictionaries sequentially (maps[0] $\rightarrow$ maps[1]), making it ideal for nested configuration stacks and scope chains.
  • namedtuple: Creates tuple subclasses with named fields. Consumes zero extra memory over standard tuples while improving code readability.
Display Options
Appearance
Text Size
100%