Back to Blog
Python

Python Abstract Class vs Interface: Key Differences

python abstract class vs interface: Compare Python's abstract base classes and typing.Protocol to decide when nominal inheritance beats structural typing for your API...

abstract base classestyping protocolsstructural typingpython abcduck typing
Diagram comparing Python abstract base class inheritance with structural protocol matching

When developers coming from Java or C# ask about python abstract class vs interface, the first thing to clarify is that Python has no native interface keyword. The language relies on duck typing, and the standard library's abc module provides abstract base classes. For structural typing, typing.Protocol (introduced in Python 3.8) fills the role that interfaces play in statically typed languages. The real question is not which keyword to use, but which abstraction mechanism fits the contract you need to express.

What an Abstract Base Class Actually Provides

The abc module lets you define a class that cannot be instantiated directly and that declares abstract methods which subclasses must implement.

from abc import ABC, abstractmethod class PaymentProcessor(ABC): @abstractmethod def charge(self, amount: float) -> str: """Charge the given amount and return a transaction ID.""" @abstractmethod def refund(self, transaction_id: str) -> None: """Refund a previously completed transaction."""

Any subclass of PaymentProcessor that fails to override both charge and refund raises TypeError at instantiation time. This is a nominal check: the subclass must explicitly inherit from PaymentProcessor. The abstract base class can also contain concrete methods, shared state, and __init_subclass__ hooks that run when a subclass is created.

What "Interface" Means in Python

Python's answer to interfaces is typing.Protocol. A Protocol defines a set of methods and attributes that an object must have, but it does not require inheritance.

from typing import Protocol class PaymentProcessor(Protocol): def charge(self, amount: float) -> str: ... def refund(self, transaction_id: str) -> None: ...

A class that implements charge and refund with matching signatures satisfies this Protocol, whether or not it inherits from it. This is structural typing: the shape of the object matters, not its declared ancestry.

Runtime Behavior Differences

The most important difference is when validation happens. ABCs enforce their contract at instantiation time. If a subclass forgets an abstract method, you get a TypeError immediately when the object is created.

Protocols, by contrast, are not enforced at runtime at all. isinstance() checks against a Protocol only work if the Protocol is decorated with @runtime_checkable, and even then the check only verifies that the methods exist, not that their signatures match. Signature validation happens only in static type checkers like mypy or Pyright.

from typing import Protocol, runtime_checkable @runtime_checkable class PaymentProcessor(Protocol): def charge(self, amount: float) -> str: ... class StripeProcessor: def charge(self, amount: float) -> str: return "txn_123" print(isinstance(StripeProcessor(), PaymentProcessor)) # True

If the method signature were wrong, for example charge(self, amount: int), the isinstance check would still return True because it only looks for attribute presence.

AspectAbstract Base ClassProtocol
Inheritance requiredYesNo
Runtime enforcementInstantiation-time checkNone unless runtime_checkable
Shared implementationYesNo
Static type checkingNominalStructural
isinstance() supportFullAttribute presence only

When an Abstract Base Class Is the Right Choice

Use an ABC when you control the class hierarchy and you want to share implementation. A base class can hold common logic, manage shared state, and define template methods that call abstract hooks.

class ReportGenerator(ABC): def generate(self) -> str: header = self._header() body = self._body() return f"{header}\n{body}" @abstractmethod def _header(self) -> str: ... @abstractmethod def _body(self) -> str: ...

Here the concrete generate method orchestrates the flow, while subclasses only supply the pieces. This is the template method pattern, and it requires inheritance. A Protocol cannot provide this shared implementation because it carries no concrete code.

ABCs are also appropriate when you need to register virtual subclasses with register(), or when you want isinstance() checks to be reliable and meaningful across a known hierarchy.

When a Protocol Is the Better Fit

Use a Protocol when you want to accept any object that satisfies a contract, without forcing it into your inheritance hierarchy. This matters when the object comes from a third-party library or from a legacy codebase that you cannot modify.

def process(payment: PaymentProcessor) -> None: txn_id = payment.charge(100.0) print(f"Charged: {txn_id}")

The process function works with any object that has a charge method with the right signature. The caller can pass a class that inherits from nothing at all. This keeps your code decoupled from concrete implementations and makes testing easier, because a simple stub class satisfies the Protocol.

Protocols also work better for narrow, single-purpose contracts. If you only need an object with a save() method, a Protocol with one method is lighter than an ABC that forces a class into a hierarchy.

Performance and Runtime Cost

Neither ABCs nor Protocols add meaningful per-call overhead in normal use. The cost appears at different points.

Creating an instance of an ABC subclass triggers a check in __init_subclass__ that verifies all abstract methods are overridden. This happens once per class creation, not per instance, so it is negligible.

Protocols have no runtime cost unless you use @runtime_checkable with isinstance(). That check walks the attributes of the object and the Protocol, so it is slower than a plain type check. If you call it in a hot loop, it can add measurable overhead. In most application code, the cost is irrelevant, but it is worth knowing that isinstance() against a Protocol is not free.

Memory usage is identical: both mechanisms are class definitions, and instances carry the same overhead.

Maintainability and Compatibility Concerns

The biggest maintainability risk with ABCs is over-engineering. Every new implementation must inherit from the base class, which couples the implementation to the abstraction. If the base class later gains a new abstract method, every subclass breaks until it implements the method. That is sometimes desirable, but it can also force changes in code that never needed the new behavior.

Protocols avoid that coupling, but they move the contract into the type checker. If your project does not run mypy or Pyright in CI, a Protocol is documentation only. A class that violates the Protocol will fail at runtime only when a missing method is actually called, and the error will be an AttributeError rather than a clear contract violation.

Python 3.8 introduced typing.Protocol. If you need to support older versions, you cannot use it without the typing_extensions backport. ABCs have been available since Python 2.6, so they work on any version you are likely to encounter.

Combining Both Approaches

A practical pattern is to define a Protocol for the external contract and an ABC for the default implementation. Libraries such as collections.abc do exactly this: MutableMapping is an ABC, but functions accept any object that structurally satisfies the mapping interface.

class PaymentProcessor(Protocol): def charge(self, amount: float) -> str: ... class BasePaymentProcessor(ABC): @abstractmethod def charge(self, amount: float) -> str: ... def charge_with_retry(self, amount: float, retries: int = 3) -> str: for attempt in range(retries): try: return self.charge(amount) except Exception: continue raise RuntimeError("Payment failed after retries")

External implementations can satisfy the Protocol without inheriting from the ABC. Internal implementations inherit from the ABC to get the retry logic. This gives you the decoupling of structural typing and the code reuse of inheritance in the same design.

The choice between an abstract class and an interface in Python is therefore a choice between nominal and structural typing. If you control the hierarchy and need shared code, use an ABC. If you want to accept any object that matches a shape, use a Protocol. If you need both, define the Protocol as the public contract and the ABC as a convenience base class that implements it.

python abstract class vs interface: Practical Usage and Code | RYUSLOG DEV