collections.abc: container interfaces

collections · Advanced · 12 min

Writing code against interfaces like Mapping or Iterable makes it accept many types, and the mixins save you dozens of methods.

collections.abc defines the interfaces behind Python's containers: Iterable, Iterator, Sized, Container, Collection, Sequence, MutableSequence, Set, Mapping, MutableMapping, plus Callable, Hashable, Awaitable and others.

Two uses:

Capability checks and annotations. isinstance(x, Mapping) is true for dicts, OrderedDicts, ChainMaps and any registered mapping, which is better than isinstance(x, dict). Simple ABCs like Iterable and Sized recognise classes structurally: any class with __iter__ counts as Iterable without inheriting. In type hints, annotate parameters with the most general ABC you need, such as Iterable[str] or Mapping[str, int] (these are subscriptable at runtime since 3.9).

Mixins. Inherit from an ABC, implement the few abstract methods, and get the rest for free. For Mapping you write __getitem__, __iter__ and __len__, and receive get, keys, items, values, __contains__ and __eq__. MutableMapping additionally needs __setitem__ and __delitem__ and provides pop, update, setdefault and more. Forgetting an abstract method makes instantiation fail with TypeError, listing what's missing.

The docs' table of ABCs lists abstract and mixin methods for each, worth bookmarking.

Example

from collections import ChainMap, OrderedDict
from collections.abc import Iterable, Mapping, MutableMapping

class CaseInsensitive(MutableMapping):
    def __init__(self, data=()):
        self._d = {}
        self.update(data)                     # mixin method
    def __getitem__(self, key): return self._d[key.lower()][1]
    def __setitem__(self, key, value): self._d[key.lower()] = (key, value)
    def __delitem__(self, key): del self._d[key.lower()]
    def __iter__(self): return (orig for orig, _ in self._d.values())
    def __len__(self): return len(self._d)

h = CaseInsensitive({"Content-Type": "json"})
print(h["content-type"], "CONTENT-TYPE" in h)   # json True
print(h.get("Accept", "*/*"), list(h))          # */* ['Content-Type']

for obj in [{}, OrderedDict(), ChainMap(), h, [("a", 1)]]:
    print(type(obj).__name__, isinstance(obj, Mapping))

class Countdown:
    def __iter__(self): return iter([3, 2, 1])
print(isinstance(Countdown(), Iterable))        # True (structural check)

Remember

  • Check isinstance(x, Mapping) rather than dict to accept all mappings
  • Inherit an ABC and implement its abstract methods to get mixin methods free
  • Annotate parameters with the most general ABC you need

Watch out for

  • Subclassing an ABC but missing one abstract method; instantiation then raises TypeError.

Check yourself

  1. To build a read-only mapping by inheriting collections.abc.Mapping, which methods must you implement?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/collections.abc.html