Abstract Base Classes — Core Concepts

The Problem ABCs Solve

Python uses duck typing: if it walks like a duck and quacks like a duck, it’s a duck. This is great for flexibility, but it means bugs hide. If you expect every plugin to implement a process() method and one doesn’t, you won’t know until that specific plugin gets called — possibly weeks into production.

Abstract Base Classes give you compile-time-ish guarantees in a dynamic language. They let you define a contract that subclasses must follow.

How It Works

The abc module provides two key tools:

  • ABC — a base class you inherit from
  • @abstractmethod — a decorator that marks methods as “must implement”

When you create a class that inherits from an ABC but doesn’t implement all abstract methods, Python raises TypeError at instantiation time — not when the method is called.

This means the error happens during testing or even import, not in production at 3 AM.

The Three Patterns

1. Pure Interface

Every method is abstract. The ABC contributes no behavior — it just defines what must exist. Think of Java interfaces: the ABC says “you must have these methods” and nothing else.

2. Partial Implementation

Some methods are abstract, others are concrete. The ABC provides shared logic (like logging or validation) while requiring subclasses to implement the core behavior. This is the most common pattern in real codebases.

3. Virtual Subclassing

You can register a class as a “virtual subclass” of an ABC without inheriting from it. This is useful when you want to say “this third-party class satisfies my interface” without modifying its source code. The register() method handles this — but note that virtual subclasses don’t get the abstract method enforcement.

Built-In ABCs You Already Use

Python’s standard library is full of ABCs you interact with daily:

  • collections.abc.Iterable — anything with __iter__
  • collections.abc.Mapping — dict-like objects
  • collections.abc.Sequence — list-like objects
  • numbers.Number — numeric types

When you write isinstance(obj, Mapping), you’re checking against an ABC. This works even for classes that never explicitly inherited from Mapping — thanks to virtual subclassing and __subclasshook__.

ABCs vs Protocols

Since Python 3.8, typing.Protocol offers structural subtyping — a more flexible alternative. Protocols check “does this object have the right methods?” at type-checking time without requiring inheritance.

FeatureABCProtocol
Requires inheritanceYes (unless registered)No
Runtime enforcementYes (TypeError on instantiation)No (type checker only)
Can provide implementationYesYes (but unusual)
Best forInternal APIs, frameworksType hints, third-party code

ABCs are best when you want runtime enforcement. Protocols are best when you want static analysis flexibility.

Common Misconception

“ABCs are just for large enterprise codebases.” Not true. Any project with a plugin system, multiple backend implementations (databases, payment providers, notification services), or team-shared interfaces benefits from ABCs. Even a two-person project avoids bugs when the contract is explicit.

When to Use ABCs

  • You have multiple implementations of the same interface
  • Forgetting a method would cause silent failures
  • You want isinstance() checks that work across unrelated class hierarchies
  • You’re building a framework or library others will extend

When to Skip Them

  • You have a single implementation (just use a regular class)
  • You’re writing quick scripts
  • Protocol-based type checking covers your needs

One thing to remember: ABCs are Python’s way of saying “I trust duck typing, but I want a safety net.” They catch the mistakes that duck typing lets slip through.

pythonoopadvanced

See Also