datarekha
Python Medium Asked at GoogleAsked at StripeAsked at PalantirAsked at Amazon

What are Abstract Base Classes in Python, how do you define them, and how do they relate to duck typing?

The short answer

Abstract Base Classes (ABCs) from the `abc` module let you declare interfaces with `@abstractmethod` — any concrete subclass that does not implement all abstract methods raises `TypeError` at instantiation. ABCs coexist with duck typing: you can register unrelated classes as virtual subclasses without inheritance, and `isinstance` checks will pass.

How to think about it

At bottom this question asks: how do you enforce a contract in Python without switching to a statically-typed language? Plain duck typing is flexible but silent — you find out a method is missing at runtime, usually at the worst possible moment. Abstract Base Classes move that discovery forward: a class that doesn’t implement the full interface refuses to be instantiated at all.

The machinery is small. ABC is a base class wired to the ABCMeta metaclass; @abstractmethod flags the methods a subclass must provide. The metaclass tracks which abstract methods are still unfilled, and when you try to construct an instance it checks that set first — so the error fires at ClassName(), not three frames deep inside some later method call.

A worked example

from abc import ABC, abstractmethod

# The interface: anything calling itself a DataSource must read() and close()
class DataSource(ABC):
    @abstractmethod
    def read(self): ...
    @abstractmethod
    def close(self): ...

# A complete implementation — instantiates fine
class CSVSource(DataSource):
    def read(self):
        return [{"row": 1}, {"row": 2}]
    def close(self):
        print("CSVSource closed")

# An incomplete one — close() is missing
class IncompleteSource(DataSource):
    def read(self):
        return []

src = CSVSource()
print("CSVSource read:", src.read())
src.close()

# The contract is enforced at construction time, not deep in a later call
try:
    IncompleteSource()
except TypeError as e:
    print("IncompleteSource():", e)

# register(): tell the ABC an unrelated class already satisfies the interface
class LegacySource:
    def read(self): return []
    def close(self): pass

DataSource.register(LegacySource)
print("LegacySource is DataSource?", isinstance(LegacySource(), DataSource))
CSVSource read: [{'row': 1}, {'row': 2}]
CSVSource closed
IncompleteSource(): Can't instantiate abstract class IncompleteSource with abstract method close
LegacySource is DataSource? True

Two behaviours to take away. IncompleteSource() raised the instant you tried to build it — naming exactly which method was missing — instead of blowing up later when something called .close(). And register() told the ABC to trust LegacySource as a DataSource with no inheritance at all, so isinstance passes. That’s how Python’s own collections.abc recognises list as a Sequence without list ever subclassing it.

Structural matching with subclasshook

For genuinely duck-typed checks — “anything with a callable read” — override __subclasshook__, and isinstance passes automatically, no registration needed:

class Readable(ABC):
    @classmethod
    def __subclasshook__(cls, subclass):
        if cls is Readable:
            return hasattr(subclass, "read") and callable(subclass.read)
        return NotImplemented
Learn it properly Inheritance vs Composition

Keep practising

All Python questions

Explore further

Skip to content