Back to Blog
Python

Python Protocol vs Abstract Base Class

python protocol vs abstract base class: Compare Python's Protocol and Abstract Base Class for defining interfaces, covering runtime enforcement, type checking, and whe...

typingabstract-base-classprotocolstructural-typinginterfaces
Illustration comparing Python Protocol and Abstract Base Class interface definitions

When you need to define an interface in Python, two standard tools come to mind: typing.Protocol and abc.ABC. Both let you declare methods that concrete classes must implement, but they enforce that contract in fundamentally different ways. Understanding python protocol vs abstract base class is a decision that affects runtime behavior, type checking, and how flexible your code remains.

What Both Approaches Solve

Both Protocol and ABC exist to formalize the shape of an object. They answer the question: "What methods and attributes must a class expose to be used here?" Without such a contract, you rely on duck typing and hope that callers pass objects with the right methods. With an explicit interface, you get early feedback when an implementation is incomplete.

The key difference lies in how the contract is verified. An abstract base class uses inheritance: a concrete class must subclass the ABC and implement its abstract methods. A protocol uses structural subtyping: any class that has the required members is considered compatible, regardless of its inheritance hierarchy.

How ABCs Enforce Structure at Runtime

An ABC is defined by subclassing abc.ABC and decorating methods with @abstractmethod. Any concrete subclass must override all abstract methods, or Python raises a TypeError at instantiation time.

from abc import ABC, abstractmethod class Repository(ABC): @abstractmethod def get(self, key: str) -> str: ... @abstractmethod def save(self, key: str, value: str) -> None: ... class InMemoryRepository(Repository): def get(self, key: str) -> str: return "value" def save(self, key: str, value: str) -> None: pass

If you forget to implement save, InMemoryRepository() raises TypeError: Can't instantiate abstract class InMemoryRepository with abstract method save. This check happens at runtime, during instantiation, not at import time.

Because the ABC requires inheritance, you cannot make an existing class conform to the interface without modifying its class hierarchy. This is a deliberate tradeoff: you get strong runtime guarantees, but you lose flexibility when working with third-party classes or when you want to avoid inheritance for other reasons.

How Protocols Enable Structural Subtyping

A Protocol is a class that inherits from typing.Protocol. It defines method signatures but does not require inheritance. Instead, type checkers like mypy or pyright verify that an object has the required attributes and methods. At runtime, a protocol is just a regular class unless you decorate it with @runtime_checkable.

from typing import Protocol, runtime_checkable @runtime_checkable class Repository(Protocol): def get(self, key: str) -> str: ... def save(self, key: str, value: str) -> None: ... class InMemoryRepository: def get(self, key: str) -> str: return "value" def save(self, key: str, value: str) -> None: pass

With @runtime_checkable, you can use isinstance(obj, Repository) and Python will check that obj has the required methods. Without it, isinstance raises TypeError because protocols are not meant for runtime checks unless explicitly enabled.

The important nuance is that @runtime_checkable only checks for the presence of methods and attributes, not their signatures. So isinstance may return True even if the method has a different parameter list. Static type checkers, however, do verify signatures precisely.

Comparing Runtime Behavior and Type Checking

AspectABCProtocol
Inheritance requiredYesNo
Runtime enforcementAt instantiationOnly with @runtime_checkable
Signature checking at runtimeNoNo (only presence)
Static type checkingYes, via subclassingYes, via structural matching
Works with third-party classesNoYes
Multiple inheritancePossible but complexNot needed

ABCs give you a hard guarantee that an object is an instance of a specific class hierarchy. Protocols give you a softer guarantee based on the shape of the object. This difference is central to the python protocol vs abstract base class decision.

When to Choose an ABC

Use an ABC when you need runtime enforcement that cannot be bypassed. If you are building a framework where users must implement a specific set of methods, and you want to fail fast with a clear error, an ABC is the safer choice. ABCs also allow you to provide default implementations for some methods while leaving others abstract.

Another reason to choose an ABC is when you want to share implementation code. An ABC can contain concrete methods that subclasses inherit, reducing duplication. Protocols, by design, do not provide implementation—they only declare the interface.

When to Choose a Protocol

Reach for a Protocol when you want to accept any object that behaves correctly, even if it does not inherit from a common base. This is especially useful when integrating with third-party libraries where you cannot modify class hierarchies. Protocols also work well with duck typing and functional programming patterns where you prefer composition over inheritance.

If you rely heavily on static type checking and want to catch interface mismatches at development time, a Protocol gives you precise signature validation without forcing a specific inheritance tree. This makes your code more flexible and easier to test with lightweight mock objects.

Performance and Overhead Considerations

The runtime cost of ABCs comes from the abc.ABCMeta metaclass and the checks performed during instantiation. This overhead is negligible for most applications, but it exists. Protocols with @runtime_checkable add a small cost when isinstance is called, because Python must inspect the target object's attributes. Without @runtime_checkable, a Protocol has no runtime overhead at all—it is purely a typing construct.

Neither approach should be a performance bottleneck in typical code. The more important consideration is the cost of a wrong interface: an ABC will raise an error early, while a Protocol may fail later if you rely on duck typing without static analysis. That tradeoff often matters more than microsecond-level runtime differences.

Common Pitfalls and Compatibility Notes

A frequent mistake with @runtime_checkable is expecting it to validate method signatures. It does not. If you need signature enforcement, you must rely on a static type checker. Another pitfall is using a Protocol as a base class for concrete implementations. While possible, it can lead to confusion because the Protocol is not meant to provide behavior.

ABCs can also cause issues when you use multiple inheritance. If two ABCs define the same abstract method, you must carefully resolve the MRO. Protocols avoid this problem entirely because they do not participate in the inheritance hierarchy.

A Practical Example: Repository Pattern

To see the difference in action, consider a repository interface used by a service layer. With an ABC, the service expects a subclass of Repository. With a Protocol, the service accepts any object with get and save methods.

# ABC version class UserService: def __init__(self, repo: Repository): self.repo = repo # Protocol version class UserService: def __init__(self, repo: Repository): # same type hint, but now structural self.repo = repo

The type hint is identical, but the runtime behavior differs. If you pass an object that does not inherit from the ABC, the ABC version will fail at instantiation of the service only if the object is checked. With a Protocol, no runtime check happens unless you explicitly call isinstance. The static type checker will catch mismatches in both cases.

The choice between python protocol vs abstract base class ultimately depends on whether you need runtime guarantees or structural flexibility. For library code that must protect against misuse, an ABC is often better. For application code that wants to stay open to different implementations, a Protocol is more idiomatic.

python protocol vs abstract base class: Practical Usage and | RYUSLOG DEV