Skip to content
  • Abstract Base Classes & Protocols · Python

Abstract Base Classes & Protocols

LearnViaNotes @learnvianotes· 5 min read

Two ways to describe “what an object must be able to do”: ABCs check it by inheritance (nominal typing), Protocols by shape (structural typing — static duck typing).

abc.ABC and @abstractmethod

An abstract base class can't be instantiated while it has abstract methods. Subclasses must implement every one of them — the error appears when you create an object, not when you call the missing method.

Python
from abc import ABC, abstractmethod

class Exporter(ABC):
    """Template method pattern: export() fixes the algorithm, subclasses fill in the steps."""

    def export(self, rows):
        return [self.header()] + [self.format_row(r) for r in rows]

    @abstractmethod
    def header(self) -> str: ...

    @abstractmethod
    def format_row(self, row) -> str: ...

class CsvExporter(Exporter):
    def header(self):
        return "name,score"
    def format_row(self, row):
        return f"{row[0]},{row[1]}"

class Broken(Exporter):
    def header(self):
        return "x"

print(CsvExporter().export([("asha", 91), ("ravi", 84)]))   # ['name,score', 'asha,91', 'ravi,84']
try:
    Broken()
except TypeError as e:
    print(e)   # Can't instantiate abstract class Broken without an implementation for abstract method 'format_row'
print(sorted(Exporter.__abstractmethods__))                  # ['format_row', 'header']

Abstract properties & class methods

Python
from abc import ABC, abstractmethod

class Shape(ABC):
    @property
    @abstractmethod                    # @abstractmethod must be the innermost decorator
    def sides(self) -> int: ...

    @classmethod
    @abstractmethod
    def unit(cls) -> "Shape": ...

    def describe(self):                # ABCs can hold ordinary, shared methods too
        return f"{type(self).__name__} with {self.sides} sides"

class Triangle(Shape):
    sides = 3                          # a plain class attribute satisfies the abstract property
    @classmethod
    def unit(cls):
        return cls()

print(Triangle.unit().describe())      # Triangle with 3 sides

collections.abc: get methods for free

The ABCs in collections.abc are mixins: implement the few abstract methods and inherit the rest. Implement __getitem__ + __len__ and Sequence gives you in, iteration, reversed, index and count.

Python
from collections.abc import Sequence

class Countdown(Sequence):
    def __init__(self, n):
        self.n = n
    def __len__(self):
        return self.n
    def __getitem__(self, i):
        if isinstance(i, slice):
            return [self[j] for j in range(*i.indices(len(self)))]
        if i < 0:
            i += self.n
        if not 0 <= i < self.n:
            raise IndexError(i)
        return self.n - i

c = Countdown(5)
print(list(c), c[-1], c[1:3])             # [5, 4, 3, 2, 1] 1 [4, 3]
print(3 in c, c.index(2), c.count(9))     # True 3 0
print(list(reversed(c)))                  # [1, 2, 3, 4, 5]
ABCYou implementYou get for free
Iterable__iter__—
Iterator__next____iter__
Sequence__getitem__, __len____contains__, __iter__, __reversed__, index, count
MutableSequence+ __setitem__, __delitem__, insertappend, extend, pop, remove, +=…
Mapping__getitem__, __iter__, __len__get, keys, items, values, ==, in
MutableMapping+ __setitem__, __delitem__pop, setdefault, update, clear…
Set__contains__, __iter__, __len__<=, |, &, -, ^, isdisjoint

isinstance with ABCs, register & __subclasshook__

Python
from collections.abc import Iterable, Mapping, Sized, Hashable, Callable

print(isinstance([1], Iterable), isinstance({}, Mapping))      # True True
print(isinstance("abc", Hashable), isinstance([], Hashable))   # True False

class Bag:                     # doesn't inherit from Sized...
    def __len__(self):
        return 0

print(isinstance(Bag(), Sized), isinstance(len, Callable))     # True True

Some ABCs (Sized, Iterable, Hashable, Callable…) check structurally through __subclasshook__: any class with the right method counts. You can also declare a virtual subclass with register.

Python
from abc import ABC, abstractmethod
import io

class Drawable(ABC):
    pass

class LegacyWidget:                    # third-party class you can't modify
    def draw(self):
        return "drawn"

Drawable.register(LegacyWidget)        # a *virtual* subclass
print(isinstance(LegacyWidget(), Drawable), Drawable in LegacyWidget.__mro__)   # True False

class Closeable(ABC):
    @abstractmethod
    def close(self): ...

    @classmethod
    def __subclasshook__(cls, C):      # structural check: "has a close() method"
        if cls is Closeable:
            return any("close" in B.__dict__ for B in C.__mro__) or NotImplemented
        return NotImplemented

print(isinstance(io.StringIO(), Closeable), isinstance(42, Closeable))   # True False

typing.Protocol — structural typing

A Protocol lists the methods a type needs. Any class with those methods matches — no inheritance, no registration. Type checkers (mypy, pyright) verify it statically; @runtime_checkable also enables isinstance.

Python
from typing import Protocol, runtime_checkable

@runtime_checkable
class SupportsArea(Protocol):
    def area(self) -> float: ...

class Square:                          # no mention of SupportsArea anywhere
    def __init__(self, s):
        self.s = s
    def area(self):
        return self.s ** 2

class Rect:
    def __init__(self, w, h):
        self.w, self.h = w, h
    def area(self):
        return self.w * self.h

def total_area(shapes: list[SupportsArea]) -> float:
    return sum(s.area() for s in shapes)

print(total_area([Square(2), Rect(2, 3)]))                     # 10
print(isinstance(Square(1), SupportsArea), isinstance("x", SupportsArea))   # True False

ABC or Protocol?

ABC (abc.ABC)Protocol (typing.Protocol)
Typing styleNominal — must inherit or registerStructural — just have the methods
EnforcedAt instantiation (TypeError)By the static type checker
Shared codeYes — concrete methods, template methodsPossible, but only for explicit subclasses
Works with third-party classesOnly via registerAutomatically
Best forFrameworks and class hierarchies you ownLibrary APIs, callbacks, “anything with .read()”

Interview angle

Why can't you instantiate a class with abstract methods?

Nominal vs structural typing?

What does collections.abc give you beyond isinstance checks?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report