Back to Blog
Python

Python Abstract Base Class: Syntax and Practical Use

python abstract base class: Define Python abstract base classes with abc.ABC and @abstractmethod: syntax, properties, super calls, and when to choose ABCs over duck ty...

abstract base classabc moduleabstractmethodduck typingProtocol
Illustration of a Python abstract base class blueprint connected to concrete subclass implementations

A Python abstract base class (ABC) is a class that declares a contract of methods and properties that subclasses must implement. The abc module provides ABC as a base class and abstractmethod as a decorator. When a subclass fails to implement every abstract member, Python refuses to instantiate it. This turns "I forgot to implement this method" from a runtime attribute error into an immediate, explicit failure at object creation.

The primary value is not performance or cleverness. It is making the required interface visible in the class definition and enforcing it mechanically. In a codebase where several classes share behavior, an ABC documents what each implementation must provide and prevents partially built objects from being created.

What an Abstract Base Class Enforces

An ABC does more than document intent. It changes the runtime behavior of class creation. When the class body executes, ABCMeta collects every method decorated with @abstractmethod into the __abstractmethods__ set. Any subclass that does not override all of those members keeps them in its own __abstractmethods__ set, which marks the subclass as abstract as well.

This enforcement applies recursively through the hierarchy. A concrete class at the bottom of the chain must implement every abstract method declared anywhere above it. If it misses one, instantiation fails with a TypeError that names the missing member.

Declaring an Abstract Base Class

The minimal declaration uses ABC as the base and @abstractmethod on each required method.

from abc import ABC, abstractmethod class PaymentProcessor(ABC): @abstractmethod def charge(self, amount: float) -> str: pass @abstractmethod def refund(self, payment_id: str) -> bool: pass

The method bodies can contain pass or .... They are never called directly unless a subclass invokes them with super(). What matters is that the decorator marks the method as abstract, which adds the method name to the class's __abstractmethods__ set.

A concrete subclass must override every abstract method.

class CreditCardProcessor(PaymentProcessor): def charge(self, amount: float) -> str: return f"charged {amount:.2f}" def refund(self, payment_id: str) -> bool: return True

If a subclass omits one of the two methods, it remains abstract itself. You can still define it and use it as a further base class, but you cannot create an instance of it.

What Happens When You Try to Instantiate

Instantiation of an ABC with unimplemented abstract methods raises a TypeError before __init__ runs.

processor = PaymentProcessor()
TypeError: Can't instantiate abstract class PaymentProcessor with abstract method refund

The error lists every missing method, which is useful when a class hierarchy spans several levels. The check happens in ABCMeta.__call__, which inspects __abstractmethods__ before calling __new__ and __init__. This means the failure is immediate and does not depend on whether the missing method would actually be called.

The same rule applies to abstract properties and abstract class methods. If any abstract member is unimplemented, instantiation fails, regardless of how the object would be used.

Abstract Properties, Class Methods, and Static Methods

@abstractmethod composes with other decorators, and the ordering matters. For a property, @abstractmethod must be the innermost decorator, directly above the function definition.

class Report(ABC): @property @abstractmethod def title(self) -> str: pass

A subclass implements it as a normal property:

class SalesReport(Report): @property def title(self) -> str: return "Sales Report"

For class methods and static methods, the same ordering rule applies: @abstractmethod sits closest to the function.

class Parser(ABC): @classmethod @abstractmethod def from_bytes(cls, data: bytes) -> "Parser": pass @staticmethod @abstractmethod def supported_extensions() -> list[str]: pass

Older code sometimes uses @abstractclassmethod and @abstractstaticmethod, but those decorators were deprecated in Python 3.3 and removed in Python 3.9. The combined form above is the current approach.

Calling Super in Abstract Methods

An abstract method can contain a real implementation that subclasses extend. This is common when the base class performs setup that every implementation needs.

class Service(ABC): @abstractmethod def start(self) -> None: self._running = True class Worker(Service): def start(self) -> None: super().start() self._queue = []

The abstract method is never invoked directly because the class cannot be instantiated. But when a subclass calls super().start(), it reaches the base implementation. This pattern keeps shared initialization in one place while still forcing each subclass to define its own start.

The tradeoff is subtle: the method is abstract, so a subclass must override it, but the base version still runs if the subclass calls super(). If a subclass forgets the super() call, the shared setup is silently skipped. That is a maintenance risk worth documenting in the class docstring.

ABC vs Duck Typing and Protocol

Python normally uses duck typing: any object with a charge method can be passed where a payment processor is expected. An ABC changes that by requiring explicit inheritance or registration.

The typing.Protocol class offers structural subtyping without inheritance. A class matches a Protocol if it has the right members, even if it does not inherit from it.

from typing import Protocol class Charger(Protocol): def charge(self, amount: float) -> str: ...

The decision between ABC and Protocol depends on whether you control the class hierarchy.

Use an ABC when you own the implementations and want to enforce the contract at runtime, especially when isinstance() checks are part of your design. isinstance(obj, PaymentProcessor) works with ABCs and their registered virtual subclasses.

Use a Protocol when you are writing a library or function that accepts third-party objects, or when forcing inheritance would be intrusive. Protocol checks are mostly static, performed by type checkers, and do not prevent instantiation at runtime.

The two approaches are not mutually exclusive. A class can inherit from an ABC and also satisfy a Protocol, and an ABC can be registered as a virtual base of another ABC.

Maintainability and Runtime Cost

The runtime cost of ABCs is concentrated at class creation time. ABCMeta builds __abstractmethods__ when the class body executes. Per-instantiation overhead is negligible; the abstract check is a set membership test. There is no meaningful performance penalty in normal application code.

The maintainability benefit is more significant. The ABC makes the required interface explicit in one place. New subclasses fail fast when they are incomplete, and code reviewers can see the contract without reading every implementation. This is especially valuable in larger codebases where several teams contribute subclasses.

The main cost is coupling. Subclasses are tied to the ABC by inheritance, which can make testing harder if the ABC carries heavy state or dependencies. If the ABC contains only abstract declarations and no concrete logic, that coupling is light. If it contains concrete methods that touch external resources, subclasses inherit that behavior and tests must account for it.

Common Mistakes and Edge Cases

The most common mistake is placing @abstractmethod in the wrong position when combined with other decorators. With @property, the abstract decorator must be below the property decorator. Reversing the order produces a class where the property is not recognized as abstract, and instantiation succeeds even though the property is missing.

Another mistake is assuming an abstract class can be instantiated if none of its abstract methods are called. The check happens at construction time, not at call time. A missing method always blocks instantiation.

A subtler case involves __init_subclass__ or custom metaclasses. If a subclass uses a different metaclass, it must be compatible with ABCMeta. A metaclass conflict raises TypeError: metaclass conflict. When you need a custom metaclass, inherit from ABCMeta instead of type.

Virtual subclasses via register() are another edge case. MyABC.register(SomeClass) makes isinstance(SomeClass(), MyABC) return True without requiring SomeClass to inherit from MyABC. This is useful for adapting third-party classes, but it does not check that the registered class actually implements the abstract methods. The contract is only enforced for real subclasses.

python abstract base class: Practical Usage and Code Example | RYUSLOG DEV