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.
Interview-ready summary: 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.