2.37. Named tuples and deques

Lists, tuples, dictionaries, and sets cover most data needs. Three other containers in the collections module fit specific problems that the built-ins handle clumsily.

2.37.1. namedtuple – typed records without a class

A plain tuple stores values by position. That’s fine for a small, short-lived 2- or 3-tuple, but past that point[0] and point[1] start to lie about what they hold. collections.namedtuple() returns a new tuple subclass whose fields have names:

>>> from collections import namedtuple
>>> Reading = namedtuple('Reading', ('temp', 'humidity', 'ts'))
>>> r = Reading(22.5, 41.0, 137204)
>>> r.temp
22.5
>>> r.humidity
41.0
>>> r[0]
22.5

The fields argument is a sequence of name strings (or one whitespace- separated string in CPython; MicroPython is stricter – pass a tuple or list).

Why use a namedtuple instead of a class?

  • It’s a tuple. Iteration, unpacking, equality, hashing, and use as a dict key all work for free.

  • It’s immutable. Reassigning r.temp = ... raises AttributeError, which is exactly what you want for a record type.

  • It costs less RAM than a class instance with the same fields – the tuple’s storage is contiguous, with no __dict__.

Compared with the equivalent class, a namedtuple declaration is one line. The trade-off is that fields are read-only – to “change” a reading you make a new one.

2.37.2. deque – a bounded ring buffer

A list is fast at the end (append / pop) and slow at the start (insert(0, ...) / pop(0) both shift every other element). A collections.deque is fast at both ends – it’s a ring buffer indexed by head and tail pointers, so append and pop on either side run in the same fixed amount of work regardless of how many items the deque holds.

Construction in MicroPython requires both an initial iterable and a max length, in that order:

>>> from collections import deque
>>> events = deque((), 5)
>>> for i in range(8):
...     events.append(i)
>>> list(events)
[3, 4, 5, 6, 7]

When a bounded deque is full, every append drops the oldest item. That makes the deque ideal for “the last N samples”, “the last N log lines”, or any rolling window that doesn’t need to grow forever.

The methods exposed on MicroPython’s deque are deliberately minimal:

  • append(x) – add to the right.

  • appendleft(x) – add to the left.

  • extend(iterable) – append each item from the iterable.

  • pop() – remove and return the right end. Raises IndexError on empty.

  • popleft() – remove and return the left end.

Notable omissions from CPython’s deque: no clear, count, index, remove, reverse, rotate, maxlen attribute, or __contains__. Iteration and subscript indexing work:

>>> events[0]
3
>>> for e in events:
...     print(e)

A typical use: keeping the last few sensor readings to detect trend changes:

history = deque((), 10)

def push(reading):
    history.append(reading)
    if len(history) == 10 and history[-1] > 2 * history[0]:
        print('reading is climbing')

2.37.3. OrderedDict – when order is part of equality

Regular dict has preserved insertion order since MicroPython 1.13 and CPython 3.7. That covers the most common reason people used to reach for collections.OrderedDict.

What OrderedDict still gives you that a plain dict does not:

  • OrderedDict equality considers order. Two regular dicts compare equal whenever they have the same key/value pairs, regardless of insertion order. Two OrderedDict instances are equal only if their pairs are in the same order:

    >>> from collections import OrderedDict
    >>> OrderedDict([('a', 1), ('b', 2)]) == OrderedDict([('b', 2), ('a', 1)])
    False
    >>> {'a': 1, 'b': 2} == {'b': 2, 'a': 1}
    True
    
  • OrderedDict is useful when you’re serialising config to a format that does care about key order (TOML, some YAML consumers), or when you want a clear documentation hint that order matters for the readers of your code.

For everyday code, prefer the built-in dict. Reach for OrderedDict only when the order-is-part-of-equality semantics or the documentary value actually buys something.

The three containers each have one narrow fit. Named tuples replace hand-rolled record classes; deques replace lists for bounded queues; ordered dicts make insertion order part of the contract.