Python Custom Subscription and Container Behavior
Learn how Python classes customize square-bracket subscription, item assignment, item deletion, reverse iteration, and formal sequence, set, and mapping interfaces.
Advanced
Python lets your own classes participate in subscription and reverse-iteration syntax. Instead of forcing callers to use methods such as
get_item() or
set_item(), a class can define the special methods that Python uses for expressions such as
obj[key],
obj[key] = value,
del obj[key], and
reversed(obj). This lesson focuses on those container-facing hooks.
Make square brackets mean something
Define
__getitem__() when a class should support subscription. Python passes the value inside the brackets to this method. Your implementation decides what that value means for the object.
|
|
The expression catalog[1] calls Catalog.__getitem__() with 1 as the subscript.
Treat a class like a sequence
For a sequence-style object, the subscript is normally an integer or a
slice. If you delegate to an internal list or tuple, Python’s usual indexing behavior—including negative indexes and slice handling—comes along naturally.
|
|
The same __getitem__() can handle an integer index and a slice because list indexing already understands both.
Understand slices as subscript values
A slice such as
obj[1:3] is handled by the same
__getitem__() hook. The value supplied to the method is a
slice object. A custom sequence can inspect it or delegate it to another sequence.
|
|
This class returns a new Window for slices and a single value for an integer index.
Support item assignment with __setitem__
Define
__setitem__() when callers should be able to assign through subscription. The method receives the subscript and the new value, so the class controls how the update happens.
|
|
The assignment scores[1] = 85 is routed to __setitem__().
Support deletion with __delitem__
Define
__delitem__() when callers should be able to remove an item with the
del statement. Like the other subscription hooks, it receives the subscript value.
|
|
The expression del registry["draft"] invokes __delitem__() with "draft".
Give reversed() a custom implementation
Define
__reversed__() when your object can provide its own reverse iterator. Python uses it for
reversed(obj) when the hook exists. This can be useful when the class has a more direct or efficient way to traverse its data backwards.
|
|
The explicit __reversed__() hook supplies the iterator used by reversed(archive).
Choose sequence or mapping behavior
The same subscription syntax can model different kinds of containers. A sequence usually interprets an integer as an index and a
slice as a range. A mapping normally interprets the subscript as a key. Your class should choose and document one clear model instead of accepting arbitrary values without meaning.
|
|
A mapping-style container uses keys and preserves mapping-style KeyError behavior.
Use the right errors for invalid subscripts
The official data model recommends
TypeError when the subscript has an inappropriate type. For sequence-like objects, an inappropriate index value should normally raise
IndexError; for mapping-like objects, a missing key should normally raise
KeyError. Delegating to list or dict often gives these behaviors automatically.
Combine the hooks into a mutable sequence
|
|
A mutable sequence can combine read, write, delete, and reverse-iteration hooks in one coherent interface.
These hooks are independent. Subscription reads, subscription writes, deletion, and reverse iteration each have their own protocol method. A class can support only the operations that make sense for its design.
Do not confuse __getitem__ with __class_getitem__
This lesson covers subscription of an instance, such as
playlist[1]. Python also has a separate mechanism for subscription of a class itself, such as
list[int]:
__class_getitem__(). That mechanism is part of generic class parameterization and is intentionally covered separately so the two protocols stay clear.
Know when custom container behavior helps
Custom subscription is useful when an object naturally behaves like a sequence or mapping and square-bracket syntax makes that interface clearer. If the operation is better understood as an action with named arguments, a normal method is usually easier to read.
Formalize a read-only sequence with Sequence
The earlier examples show how indexed subscription works. When a class should explicitly promise the standard read-only sequence interface, inherit from
collections.abc.Sequence. Implement
__getitem__() for integer indexes and slices, and
__len__() for the number of items. The ABC then supplies common operations such as
count(),
index(), iteration, membership, and reverse iteration.
|
|
The tuple provides the indexed storage. Implementing the two required methods lets Sequence provide count(), index(), membership, iteration, and reverse iteration.
These inherited operations may call
__getitem__() repeatedly. That is fine for storage with fast indexed access, such as a tuple, but a class with expensive indexing may need to override mixin methods for better performance. Inherit from
MutableSequence instead only when item replacement, deletion, and insertion are part of the intended interface; it requires
__setitem__(),
__delitem__(), and
insert() in addition to sequence lookup and length.
Quick check
Which pair lists the abstract methods a concrete collections.abc.Sequence subclass must implement?
required_sequence_methods =
print(required_sequence_methods)
Formalize a read-only mapping with Mapping
The custom subscription examples above show which special methods make bracket syntax work. When a class should present a dictionary-like interface to other code, inherit from
collections.abc.Mapping to declare that contract and receive standard read-only mapping mixins.
A Mapping implementation must provide
__getitem__(),
__iter__(), and
__len__(). Iteration yields keys, not key-value pairs. The abstract base class supplies useful operations such as
keys(),
items(),
values(),
get(), and membership testing, built on those methods.
|
|
Three required methods are enough for the ABC to provide familiar read-only mapping operations.
Choose
MutableMapping only when the type also supports meaningful item assignment and deletion. It adds
__setitem__() and
__delitem__() to the required interface and supplies mutation helpers such as
update(),
pop(), and
setdefault(). Do not inherit a mutable interface merely to obtain convenience methods if the object is intentionally read-only.
Quick check
Which tuple lists the three abstract methods a read-only Mapping subclass must implement?
required_methods =
print(required_methods)
Formalize set behavior with Set
Some custom containers represent unordered collections of distinct values rather than positions or key-value pairs. To advertise the standard read-only set interface, inherit from
collections.abc.Set. This formalizes how your object participates in membership tests and mathematical set operations without re-teaching the built-in set operations from the Tuples and Sets lesson.
A concrete Set subclass must implement
__contains__(),
__iter__(), and
__len__(). The ABC provides comparisons,
&,
|,
-,
^, and
isdisjoint() from that contract.
|
|
The three primitive methods let the Set mixin implement intersection, subset comparison, and disjointness. Sorting is used only to make the displayed result deterministic; sets themselves do not promise an iteration order.
Set operations that create another result rely on
_from_iterable(), whose default implementation calls the class constructor with an iterable. Keep that constructor form or override the classmethod when your custom type needs different construction. Use
collections.abc.MutableSet only when mutation is part of the intended contract; it additionally requires
add() and
discard() and supplies mutating helpers. The Set ABC does not automatically make an object hashable.
Quick check
Which three abstract methods must a concrete collections.abc.Set subclass provide?
required_set_methods =
print(required_set_methods)
Key takeaways
Key takeaways
- __getitem__() implements instance subscription such as obj[key].
- __setitem__() and __delitem__() add subscription assignment and deletion behavior.
- A sequence-style object usually accepts integer indexes and slice objects; a mapping-style object uses keys.
- __reversed__() supplies a custom reverse iterator for reversed(obj).
- Choose the smallest consistent set of container hooks your class actually needs.
- __class_getitem__() is a separate protocol for class subscription and generic parameterization.
- Inheriting collections.abc.Sequence formalizes indexed access and length while providing common read-only sequence operations.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.