Skip to main content

Protocols, special methods, and __slots__

advanced25 min readLesson 110 of 169

Make your objects indistinguishable from built-ins โ€” then make them lean.

Protocols, special methods, and __slots__

Python's built-in behaviors are all data-model hooks. Objects "become" iterable, sized, comparable, or callable by defining the right special method:

| You write | Python calls | Enables | |---|---|---| | len(x) | x.__len__ | sizing | | for a in x | iter(x) โ†’ x.__iter__ | iteration | | x[k] | x.__getitem__ | indexing / sequences | | k in x | x.__contains__ (or falls back to __iter__) | membership | | x == y | x.__eq__ | equality | | x() | x.__call__ | callable objects | | with x: | x.__enter__ / x.__exit__ | context managers |

Slicing arrives as slice objects: x[1:5] calls __getitem__(slice(1, 5, None)). A robust __getitem__ handles both int and slice:

def __getitem__(self, index):
    if isinstance(index, slice):
        return type(self)(self._data[index])
    return self._data[index]

Structural typing with typing.Protocol (3.8+) lets a checker verify "has these methods" without inheritance โ€” duck typing, statically checked:

from typing import Protocol, runtime_checkable

@runtime_checkable
class Closeable(Protocol):
    def close(self) -> None: ...

def shutdown(x: Closeable) -> None:
    x.close()

__slots__ replaces the per-instance __dict__ with fixed slots: less memory for millions of instances, faster attribute access, and a typo becomes AttributeError instead of a silent new attribute. Cost: no dynamic attributes unless you add __dict__ back, and care needed with multiple inheritance (two slotted parents with nonempty slots conflict).

class Vector:
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x, self.y = x, y

Now practice

Custom Container PracticeBuild a collection that behaves like a built-in โ€” then make it lean with __slots__.3 challenges ยท ยท ~22 min