Python Architecture: Decoupling with Protocols and Abstract Base Classes

Overview of Structural and Nominal Typing

Modern Python development offers two primary ways to define interfaces:

(ABCs) and
Protocols
. While they appear similar, they serve distinct philosophical roles in software design. ABCs represent nominal typing, where relationships must be explicitly declared through inheritance. Protocols, introduced in Python 3.8, embrace structural typing. This mechanism, often described as static duck typing, allows the type system to verify compatibility based on an object's methods rather than its family tree. Choosing between them determines how your system handles dependencies and how strictly you want to enforce class hierarchies.

Prerequisites

To follow this guide, you should possess a foundational understanding of Object-Oriented Programming (OOP) in Python. Knowledge of class inheritance, decorators, and basic type hinting is essential. Familiarity with

like
Mypy
will help you visualize the differences in how these two approaches flag errors during development.

Key Libraries & Tools

  • abc module: The standard library package containing ABC and @abstractmethod for creating formal interfaces.
  • typing module: Provides the Protocol class used for structural subtyping and interface definition.
  • Mypy: A static type checker that validates whether your classes satisfy the requirements of a specific Protocol or ABC.
Python Architecture: Decoupling with Protocols and Abstract Base Classes
Protocol Or ABC In Python - When to Use Which One?

Code Walkthrough: Implementing ABCs and Protocols

Defining an Abstract Base Class

In the ABC model, you define a contract that subclasses must fulfill. The relationship is rigid; the subclass must import and inherit from the parent.

from abc import ABC, abstractmethod

class Device(ABC):
    @abstractmethod
    def connect(self) -> None:
        pass

class HueLight(Device):
    def connect(self) -> None:
        print("Connecting Hue Light...")

If HueLight fails to implement connect, Python prevents instantiation immediately. The error surfaces the moment you try to create the object.

Implementing Structural Protocols

Protocols remove the need for explicit inheritance. You define the interface in the module that consumes the service, following the Interface Segregation Principle.

from typing import Protocol

class DiagnosticSource(Protocol):
    def status_update(self) -> str:
        ...

def collect_diagnostics(source: DiagnosticSource):
    print(f"Log: {source.status_update()}")

Now, any object with a status_update method satisfies the DiagnosticSource requirement. The consumer defines what it needs, and the provider doesn't need to know the consumer exists. This significantly reduces coupling between modules.

Syntax Notes

  • Ellipsis (...): When defining Protocols, we use the ellipsis rather than pass or a body to indicate that the method is a structural requirement with no implementation.
  • Decorators: ABCs require the @abstractmethod decorator to identify methods that must be overridden. Protocols do not require decorators for their members.

Practical Examples

In a complex

(IoT) system, you might use ABCs for a core internal library where you control all subclasses and want to provide shared functionality via inheritance. Conversely, use Protocols when integrating with third-party libraries. If a third-party Camera class has a connect() method but doesn't inherit from your Device ABC, a Protocol allows your system to treat it as a valid device without modifying the external library's source code.

Tips & Gotchas

  • Error Timing: ABCs fail at instantiation. Protocols fail when the object is passed into a function that expects the protocol type. This can lead to later-stage errors if you aren't using a static type checker.
  • Multiple Inheritance: Splitting a large ABC into smaller ones often leads to the complexity of multiple inheritance. Protocols make this process natural; a single class can satisfy five different protocols without inheriting from any of them.
  • Shared Logic: If you need to provide default method implementations to all child classes, stick with ABCs. Protocols are purely for defining interfaces, not for sharing logic.
Python Architecture: Decoupling with Protocols and Abstract Base Classes

Fancy watching it?

Watch the full video and context

4 min read