Filter concepts by levelShowing all levels.

Python · Abstract classes and interfaces

Abstract classes and interfaces

Enforcing a required interface with abc, versus trusting duck typing and letting a type checker verify the shape instead

  • ABC + @abstractmethodblocks instantiation until every required method is overridden
  • Duck typingPython calls obj.method() by trying it, never by checking the class
  • Protocolnames a shape a type checker can verify, no inheritance required
  • Nominal vs structuralABC requires a declared relationship; Protocol only requires a matching shape

1.The abc module

abc is the standard-library module behind Python's abstract base classes — it supplies the ABC base class and the @abstractmethod decorator that together stop a class from being instantiated until it fills in every required method.

Key features

  • abc supplies ABC (a ready-made base class) and @abstractmethod (a decorator)
  • Inheriting from ABC alone changes nothing until a method is marked @abstractmethod
  • ABCMeta is the metaclass ABC is built from — used directly only for advanced cases
  • abc.abstractmethod must sit closest to the function, decorators apply bottom-up
  • register() can declare an unrelated class a "virtual subclass" without inheriting

What abc actually exports

What abc actually exports
NameWhat it is for
ABCa ready-made base class using ABCMeta — inherit from this, not ABCMeta directly
ABCMetathe metaclass that enforces abstract methods — abc.ABC is built from it
abstractmethoddecorator marking a method every concrete subclass must override
abstractpropertydeprecated — use @property with @abstractmethod stacked instead

Command

python
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        ...

ABC (not ABCMeta) is what actually enforces @abstractmethod — inheriting from ABC is what makes an unimplemented abstract method block instantiation.

2.ABC

ABC is a base class from the abc module. Inheriting from it (directly or through another ABC) lets a class use @abstractmethod to require its subclasses to implement certain methods before they can be instantiated.

Key features

  • class Shape(ABC): opts a class into abstract-method enforcement
  • An ABC can still define normal (non-abstract) methods, used as-is by every subclass
  • A subclass is only concrete once every inherited abstract method is overridden
  • isinstance()/issubclass() work on ABCs exactly like any other class
  • ABC.register(OtherClass) makes OtherClass a virtual subclass without real inheritance

What counts as satisfying an ABC

What counts as satisfying an ABC
SituationCan it be instantiated?
Subclass overrides every abstract methodYes — fully concrete
Subclass overrides only some abstract methodsNo — still abstract, same error
Subclass overrides noneNo — identical to instantiating the ABC itself
Unrelated class registered via ABC.register(cls)Yes for isinstance() checks — cls itself is untouched
A subclass must complete every abstract method to become concrete

class Shape(ABC)

area() marked @abstractmethod

Shape()

TypeError — still abstract

class Circle(Shape)

defines area() -> concrete, instantiable

  1. class Shape(ABC) — area() marked @abstractmethod
  2. Shape() — TypeError — still abstract
  3. class Circle(Shape) — defines area() -> concrete, instantiable

Command

python
from abc import ABC, abstractmethod

class Base(ABC):
    @abstractmethod
    def required(self):
        ...

    def helper(self):            # normal method, inherited as-is
        return "shared logic"

A subclass of an ABC stays exactly as abstract as the ABC itself until every one of its @abstractmethods is overridden — one out of two is not enough.

3.@abstractmethod

@abstractmethod marks a method as required — it sets a flag (__isabstractmethod__ = True) that ABCMeta checks at instantiation time. The decorator alone does nothing without a base class using ABCMeta (usually via ABC).

Key features

  • @abstractmethod just sets func.__isabstractmethod__ = True on the function
  • It has no effect at all unless the enclosing class uses ABCMeta (via ABC)
  • A subclass can still call an abstract method's body via super() if one is provided
  • Stacked with @property/@classmethod: @abstractmethod goes innermost, closest to def
  • The check happens once, at instantiation — not on every method call afterward

Stacking @abstractmethod with other decorators

Stacking @abstractmethod with other decorators
CombinationCorrect order (top to bottom)
Abstract property@property then @abstractmethod
Abstract classmethod@classmethod then @abstractmethod
Abstract staticmethod@staticmethod then @abstractmethod
Plain abstract method@abstractmethod alone

Command

python
from abc import ABC, abstractmethod

class Base(ABC):
    @abstractmethod
    def required(self):
        ...              # or a default body a subclass can call via super()

@abstractmethod only ever checks that a subclass defines the SAME NAME — never that the override is correct, and never anything beyond instantiation time.

4.Python's duck typing

Duck typing means Python calls obj.method() based only on whether obj actually has method — never on obj's declared type or an isinstance() check. Any object with a matching .read() works where a "file-like object" is expected.

Key features

  • "If it walks like a duck and quacks like a duck, it is a duck"
  • A function calling obj.method() never checks obj's type first — it just calls
  • Any object with matching methods works, regardless of inheritance or isinstance()
  • Failure is deferred: AttributeError happens only when the missing call is reached
  • Contrasts with type-checked languages that require declaring an interface up front

Duck typing vs explicit type checking, for the same function

Duck typing vs explicit type checking, for the same function
StyleWhat the function does
Duck typingobj.read() — calls it, trusts obj has it
isinstance() checkif isinstance(obj, SomeType): obj.read() — checks the class first
hasattr() checkif hasattr(obj, "read"): obj.read() — checks the method exists first
try/except (EAFP)try: obj.read() except AttributeError: ... — attempts, handles failure
Any object that answers the right call passes

FileReader.read()

has the method

NetworkStream.read()

unrelated class, same method

load(source)

calls .read() on either, no type check

  1. FileReader.read() — has the method
  2. NetworkStream.read() — unrelated class, same method
  3. load(source) — calls .read() on either, no type check

Command

python
def load(source):
    return source.read()   # works on ANY object with a .read() method

Python calls obj.method() by trying it, never by checking obj's declared type first — any object with the right method works.

5.Protocols

typing.Protocol lets you name a shape ("anything with a .read() method") as a real type, so a type checker like mypy can verify duck typing statically. A class satisfies a Protocol just by having the right methods — no inheritance needed.

Key features

  • from typing import Protocol — subclass it to define a named structural type
  • A class satisfies a Protocol by having matching methods, not by inheriting from it
  • @runtime_checkable lets isinstance()/issubclass() check a Protocol at runtime
  • Without @runtime_checkable, a Protocol is a static-analysis-only annotation
  • Protocols formalize duck typing for type checkers — no runtime behaviour by default

Protocol vs. ABC, for expressing the same requirement

Protocol vs. ABC, for expressing the same requirement
ApproachHow a class satisfies it
abc.ABC + @abstractmethodmust explicitly inherit from the ABC
typing.Protocoljust needs matching methods — no inheritance required
Protocol + @runtime_checkablematching methods AND isinstance() works at runtime
Plain duck typing, no Protocolmatching methods, but nothing for a type checker to verify

Command

python
from typing import Protocol

class Readable(Protocol):
    def read(self) -> str: ...

A Protocol is satisfied just by having matching methods — no inheritance. @runtime_checkable is needed before isinstance() works, and it only checks names.

6.Structural typing

Structural typing means two types are compatible if they have the same shape (matching methods/attributes) — regardless of name or inheritance. It is the general principle; duck typing is its runtime form, Protocol is its type-checked form.

Key features

  • Structural typing: compatible if the SHAPE matches — same methods/attributes
  • Nominal typing (opposite): compatible only if explicitly declared (extends/implements)
  • Duck typing is structural typing's runtime, no-type-checker-needed form
  • typing.Protocol is structural typing made visible to a static type checker
  • Python's inheritance/isinstance() is nominal by default; Protocol adds a structural option

Structural vs. nominal typing, same requirement expressed both ways

Structural vs. nominal typing, same requirement expressed both ways
Typing styleHow compatibility is judged
Structural (Python default / TypeScript)does the object have the right shape?
Nominal (Java, C#, explicit ABC inheritance)was the type explicitly declared to implement this?
Python + typing.Protocolstructural, but visible to a type checker
Python + abc.ABCnominal — must explicitly inherit to count
Two ways to decide "is this compatible?"

Nominal typing

  • Must explicitly declare the relationship
  • class Dog(Animal): or implements Interface
  • abc.ABC's enforced inheritance is Python's nominal option

Structural typing

  • Judged by shape alone — matching methods
  • No declared relationship needed at all
  • Python's normal duck typing, and typing.Protocol
  • Nominal typing
    • Must explicitly declare the relationship
    • class Dog(Animal): or implements Interface
    • abc.ABC's enforced inheritance is Python's nominal option
  • Structural typing
    • Judged by shape alone — matching methods
    • No declared relationship needed at all
    • Python's normal duck typing, and typing.Protocol

Command

python
# structural: no shared base class, still compatible
class A:
    def method(self): ...

class B:
    def method(self): ...   # same shape as A, unrelated

Structural typing judges compatibility by shape (matching methods); nominal typing (abc.ABC) needs an explicit declared relationship instead.

Remember

  • ABC (not ABCMeta) is what enforces @abstractmethod — inheriting from ABC is what blocks instantiation.
  • A subclass stays exactly as abstract as its base until EVERY inherited @abstractmethod is overridden.
  • @abstractmethod only checks that a subclass defines the same NAME — never that the override is correct.
  • Python calls obj.method() by trying it, never by checking obj's declared type first.

Pro tip

  • Stack @abstractmethod closest to def — @property goes outermost, @abstractmethod innermost.
  • @runtime_checkable is required before isinstance() works on a Protocol, and even then only checks names, not signatures.
  • Reach for a Protocol instead of an ABC when the goal is "any matching shape should work," not "only declared subclasses."
  • ABC.register(cls) grants isinstance() compatibility only — it grants none of the ABC's actual methods.

Next up

  • Properties
  • Dunder methods
  • Advanced OOP

Sources

  • abc — docs.python.org/3/library/abc.html
  • typing.Protocol — docs.python.org/3/library/typing.html#typing.Protocol
  • Every example executed on Python 3.14.3 · checked 2026-08-21

Spotted a mistake on this page? Tell us.