What's the difference between an abstract base class (ABC) and a `Protocol`?
Quick Answer
ABC (via abc.ABC/@abstractmethod) enforces nominal typing. A class must explicitly inherit from the ABC to be recognized as implementing it, and Python raises TypeError at instantiation if abstract methods are left unimplemented. Protocol (PEP 544) enforces structural typing. A class satisfies the protocol just by having matching methods, with no inheritance required, checked statically by mypy (and optionally at runtime with @runtime_checkable).
Detailed Answer
ABC: nominal typing, enforced at instantiation
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return 3.14159 * self.r ** 2
Shape() # TypeError: Can't instantiate abstract class Shape
Circle(5).area() # 78.5...
class Incomplete(Shape):
pass
Incomplete() # TypeError: Can't instantiate abstract class Incomplete
# with abstract method area
Shape explicitly declares a contract. Circle must explicitly inherit
from Shape to be treated as one, and Python enforces at runtime that
every abstract method is overridden before a concrete subclass can even be
instantiated.
Protocol: structural typing, checked statically
from typing import Protocol, runtime_checkable
@runtime_checkable
class HasArea(Protocol):
def area(self) -> float: ...
class Circle: # note: NOT inheriting from HasArea at all
def __init__(self, r):
self.r = r
def area(self):
return 3.14159 * self.r ** 2
def total_area(shapes: list[HasArea]) -> float:
return sum(s.area() for s in shapes)
total_area([Circle(5)]) # type-checks fine — Circle matches structurally
isinstance(Circle(5), HasArea) # True, because @runtime_checkable was used
Circle never mentions HasArea. mypy (or isinstance, with
@runtime_checkable) verifies the match purely by comparing method
signatures.
Key differences
ABC | Protocol | |
|---|---|---|
| Typing style | Nominal (explicit inheritance) | Structural (duck typing, formalized) |
| Enforcement | Runtime, at instantiation | Static, by type checker (isinstance only with @runtime_checkable, and only checks method presence, not signatures) |
| Coupling | Implementer must know about/import the ABC | Implementer needs no knowledge of the Protocol |
| Best for | A shared base with common implementation/contract you own | Describing a shape third-party or unrelated classes already have |
When to use which
Use ABC when you own the class hierarchy and want a hard runtime
guarantee that subclasses implement required methods (and possibly want
to share concrete helper methods too). Use Protocol when you're
describing an interface that unrelated or third-party classes already
satisfy, and you only need static verification, not a runtime contract.
ABC is nominal typing with runtime
enforcement — you must inherit from it, and instantiation fails if
abstract methods are missing. Protocol is structural typing checked
statically — any class with matching methods satisfies it, no inheritance
required, which is ideal for describing capabilities of classes you don't
control.