What's the difference between an abstract base class (ABC) and a `Protocol`?

6 minadvancedoopabcprotocoltyping

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

ABCProtocol
Typing styleNominal (explicit inheritance)Structural (duck typing, formalized)
EnforcementRuntime, at instantiationStatic, by type checker (isinstance only with @runtime_checkable, and only checks method presence, not signatures)
CouplingImplementer must know about/import the ABCImplementer needs no knowledge of the Protocol
Best forA shared base with common implementation/contract you ownDescribing 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.