Choosing Abstract Base Classes
Pythonfrom collections.abc import Mapping, Sequence, Iterable # Duck-typing check: does obj behave like a mapping? if isinstance(obj, Mapping): for key in obj: print(key, obj[key]) # Custom container: implement minimum methods, get the rest free class FrozenDict(Mapping): def __init__(self, data): self._data = dict(data) def __getitem__(self, key): return self._data[key] def __iter__(self): return iter(self._data) def __len__(self): return len(self._data) # __contains__, keys, items, values, get, __eq__, __ne__ come for free
Progress:
- Step 1: Identify what behavior is needed (iteration? indexing? mutation? hashing?)
- Step 2: Pick the narrowest ABC that matches (don't over-implement)
- Step 3: Check the ABC's "Abstract Methods" — implement only those
- Step 4: Check "Mixin Methods" — these come free from the abstract ones
- Step 5: Use
isinstance()/issubclass()for duck-typing checks instead of concrete types - Step 6: Register unrelated classes with
.register()if virtual subclassing is needed
Reference table (most-used ABCs)
| ABC | Abstract Methods | Inherits From | Use for |
|---|---|---|---|
Iterable | __iter__ | — | anything you can loop over |
Iterator | __next__ | Iterable | stateful iteration objects |
Container | __contains__ | — | in operator support |
Sized | __len__ | — | len() support |
Hashable | __hash__ | — | usable as dict key / set member |
Collection | __contains__, __iter__, __len__ | Sized, Iterable, Container | general-purpose container check |
Sequence | __getitem__, __len__ | Reversible, Collection | ordered, indexable (list-like) |
MutableSequence | + __setitem__, __delitem__, insert | Sequence | mutable list-like |
Mapping | __getitem__, __iter__, __len__ | Collection | dict-like read access |
MutableMapping | + __setitem__, __delitem__ | Mapping | dict-like read/write |
Set | __contains__, __iter__, __len__ | Collection | set-like, supports &, |, -, ^ |
MutableSet | + add, discard | Set | mutable set-like |
Callable | __call__ | — | function-like objects |
Example 1:
Input: "I need a class that supports len(), in, and iteration, but I don't need indexing or mutation."
Output: Inherit from Collection and implement __contains__, __iter__, __len__. Don't reach for Sequence — it forces __getitem__ and implies order/indexing you don't need.
Example 2:
Input: "How do I check if a function argument is 'list-like' without requiring it to actually be a list?"
Output:
Pythonfrom collections.abc import Sequence def process(items): if not isinstance(items, Sequence): raise TypeError("items must be a Sequence") return items[0], len(items)
This accepts tuple, custom Sequence subclasses, etc., while rejecting str only if that's explicitly desired (note str is a Sequence — guard separately if needed).
Example 3:
Input: "Building a custom read-only set-like collection backed by a database query."
Output: Inherit from Set, implement __contains__, __iter__, __len__. Get __le__, __lt__, __gt__, __ge__, __eq__, __and__, __or__, __sub__, __xor__, isdisjoint for free.
- Implement the minimum abstract set; let mixins provide the rest. Overriding a mixin method is fine if you have a faster implementation (e.g., custom
__contains__for O(1) lookup backed by a hash index). - Prefer ABC checks over concrete type checks (
isinstance(x, Mapping)overisinstance(x, dict)) to support duck-typing and third-party implementations. - Use
Iterable/Iteratordistinction correctly: anIterableproduces anIteratorvia__iter__; don't conflate the two when type-checking. - Register virtual subclasses when a class already satisfies the interface but can't/shouldn't inherit:
Sequence.register(MyExternalType). - Remember
str,bytes,rangeareSequence— guard against accidentally treating strings as sequences of characters when you meant "list-like". - For generic type hints, prefer
collections.abcclasses overtypingequivalents (e.g.,collections.abc.Sequence[int]) —typingaliases are deprecated in favor of these.
- Don't implement
Mapping/Sequencefrom scratch — subclass the ABC and only fill abstract methods; reimplementingkeys(),items(),get(), etc. manually is redundant and error-prone. - Don't use
Sequencewhen you just needIterable— forcing__getitem__/__len__unnecessarily overconstrains simple generators/streams. - Don't forget
Hashableimmutability contract — if you implement__hash__, ensure the object's hash-relevant state never changes after creation. - Don't check
isinstance(x, list)for "list-like" duck typing — this excludes valid alternative implementations; useMutableSequenceorSequence. - Don't assume
Setmixins imply sorted order —Setoperations don't guarantee ordering; that's aSequenceconcern.