Skip to content
  • More collections: namedtuple, OrderedDict, ChainMap · Python

More collections: namedtuple, OrderedDict, ChainMap

LearnViaNotes @learnvianotes· 5 min read

The rest of the collections toolbox — lightweight records, counting arithmetic, order-aware dicts, layered lookups — plus when to use each.

namedtuple — a tuple with field names

Python
from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
x, y = p                             # still a tuple: unpacking, indexing, hashing
print(p.x, p[1], x + y)              # 3 4 7
print(p._replace(x=10), p)           # Point(x=10, y=4) Point(x=3, y=4)
print(p._asdict())                   # {'x': 3, 'y': 4}
print(Point._fields)                 # ('x', 'y')
print(Point._make([5, 6]))           # Point(x=5, y=6)

Conn = namedtuple("Conn", "host port timeout", defaults=[30])
print(Conn("db", 5432))              # Conn(host='db', port=5432, timeout=30)

typing.NamedTuple is the class-syntax version — same runtime behaviour, but with type hints, docstrings and methods.

Python
from typing import NamedTuple

class Employee(NamedTuple):
    name: str
    dept: str = "eng"

    def badge(self) -> str:
        return f"{self.name}@{self.dept}"

e = Employee("asha")
print(e, e.badge())          # Employee(name='asha', dept='eng') asha@eng
try:
    e.name = "ravi"
except AttributeError:
    print("immutable")       # immutable
namedtuple / NamedTuple@dataclassdict
MutableNoYes (or frozen=True)Yes
Accessp.x and p[0]p.xp['x']
HashableYesOnly if frozenNo
Best forSmall immutable records, returning several valuesRich objects with defaults & methodsDynamic / JSON-like data

Counter — counting with arithmetic

Python
from collections import Counter

inventory = Counter(apple=3, pear=1)
inventory.update(["apple", "kiwi"])          # add counts
print(inventory)                             # Counter({'apple': 4, 'pear': 1, 'kiwi': 1})
inventory.subtract({"pear": 2})              # can go negative
print(inventory["pear"], inventory.total())  # -1 4

a, b = Counter("aabbc"), Counter("abd")
print(a + b)          # Counter({'a': 3, 'b': 3, 'c': 1, 'd': 1})
print(a - b)          # Counter({'a': 1, 'b': 1, 'c': 1})   ← drops counts <= 0
print(a & b)          # Counter({'a': 1, 'b': 1})           ← min of each
print(sorted(Counter(x=2, y=1).elements()))   # ['x', 'x', 'y']

defaultdict variations

Python
from collections import defaultdict

seen_first = defaultdict(lambda: "unknown")     # any zero-arg callable
print(seen_first["x"])                          # unknown

def tree():
    return defaultdict(tree)                    # infinitely nested dict
t = tree()
t["india"]["ts"]["hyd"] = 1
print(t["india"]["ts"]["hyd"], "ap" in t["india"])   # 1 False

totals = defaultdict(int)
for dept, amount in [("eng", 5), ("ops", 2), ("eng", 3)]:
    totals[dept] += amount
print(dict(totals))                              # {'eng': 8, 'ops': 2}

OrderedDict — order you can manipulate

Plain dicts keep insertion order since 3.7, so OrderedDict is now for its extras: move_to_end, popitem(last=False), and order-sensitive equality.

Python
from collections import OrderedDict

a = OrderedDict(x=1, y=2)
b = OrderedDict(y=2, x=1)
print(a == b, dict(a) == dict(b))     # False True   ← OrderedDict compares order

a.move_to_end("x")                    # x becomes the newest
print(list(a))                        # ['y', 'x']
print(a.popitem(last=False))          # ('y', 2)   ← FIFO pop of the oldest
Python — LRU cache (a classic interview question)
from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.data = OrderedDict()

    def get(self, key):
        if key not in self.data:
            return -1
        self.data.move_to_end(key)            # mark as recently used
        return self.data[key]

    def put(self, key, value):
        self.data[key] = value
        self.data.move_to_end(key)
        if len(self.data) > self.capacity:
            self.data.popitem(last=False)     # evict least recently used

cache = LRUCache(2)
cache.put(1, "a"); cache.put(2, "b")
cache.get(1)                                  # 1 is now most recent
cache.put(3, "c")                             # evicts 2
print(list(cache.data), cache.get(2))         # [1, 3] -1

ChainMap — layered lookups

ChainMap(a, b, c) searches several dicts in order without copying them. Writes go to the first map. Perfect for defaults → config file → environment → command-line layering, or nested scopes.

Python
from collections import ChainMap

defaults = {"theme": "light", "lang": "en", "debug": False}
user = {"theme": "dark"}
cli = {"debug": True}

settings = ChainMap(cli, user, defaults)
print(settings["theme"], settings["lang"], settings["debug"])   # dark en True

settings["lang"] = "te"                  # written to the first map (cli)
print(cli, defaults["lang"])             # {'debug': True, 'lang': 'te'} en

scope = ChainMap({"x": 1})
inner = scope.new_child({"x": 2})        # push a new scope
print(inner["x"], inner.parents["x"])    # 2 1

UserDict & UserList — safe subclassing

Subclassing dict directly is tricky: update() and the constructor bypass your overridden __setitem__. UserDict stores the data in self.data and routes every write through your methods.

Python
from collections import UserDict

class LowerDict(dict):
    def __setitem__(self, k, v):
        super().__setitem__(k.lower(), v)

class LowerUserDict(UserDict):
    def __setitem__(self, k, v):
        super().__setitem__(k.lower(), v)

print(LowerDict({"A": 1}))                    # {'A': 1}   ← override bypassed!
print(LowerUserDict({"A": 1}).data)           # {'a': 1}

Two more handy containers

Python
from array import array
from types import SimpleNamespace

nums = array("i", [1, 2, 3])        # compact typed array of C ints
nums.append(4)
print(nums.tolist(), nums.itemsize) # [1, 2, 3, 4] 4

cfg = SimpleNamespace(host="localhost", port=8000)   # attribute bag
cfg.debug = True
print(cfg.port, cfg)   # 8000 namespace(host='localhost', port=8000, debug=True)

Choosing the right container

NeedUse
Immutable record with named fieldsnamedtuple / typing.NamedTuple
Count things, combine countsCounter
Group values / auto-create missing entriesdefaultdict
Reorder keys, LRU cache, FIFO dictOrderedDict
Look up through several dicts in priority orderChainMap
Queue / sliding windowdeque
Custom dict/list behaviourUserDict / UserList
Millions of numbers, low memoryarray (or NumPy)
Quick attribute bagtypes.SimpleNamespace

Why use OrderedDict now that dict preserves insertion order?

Implement an LRU cache in O(1) per operation.

namedtuple or dataclass?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report