Overview Software design often requires a contract between different parts of a system. In Python, we primarily use Abstract Base Classes (ABCs) and Protocols to define these contracts. While they appear similar, they represent two fundamentally different philosophies: nominal typing and structural typing. This guide explores how to implement both and why choosing the right one can dramatically reduce coupling in your applications. Prerequisites To follow this guide, you should understand: * Intermediate Python (Classes and Type Hinting). * Basic software design principles like inheritance. * Standard library usage (specifically the `abc` and `typing` modules). Key Libraries & Tools * `abc`: The built-in module for defining Abstract Base Classes. * `typing`: Provides the `Protocol` class for structural typing. * Mypy: A static type checker to verify implementation adherence (recommended). Code Walkthrough Implementation with Abstract Base Classes ABCs use nominal typing. You must explicitly inherit from the base class for the type system to recognize the relationship. ```python from abc import ABC, abstractmethod class Device(ABC): @abstractmethod def connect(self) -> None: ... class HueLight(Device): def connect(self) -> None: print("Connecting to Hue Light") ``` Here, `HueLight` is a `Device` because we said so in the class definition. If `HueLight` fails to implement `connect`, Python prevents instantiation immediately. Implementation with Protocols Protocols use structural typing (duck typing). Relationship is determined by the presence of methods, not inheritance. ```python from typing import Protocol class Device(Protocol): def connect(self) -> None: ... class SmartSpeaker: def connect(self) -> None: print("Connecting to Speaker") ``` `SmartSpeaker` does not inherit from `Device`. However, because it has a `connect` method, it satisfies the Protocol. The type checker validates this at the point of usage, such as when passing the speaker into a function expecting a `Device`. Syntax Notes * **ABC Decorators**: Use `@abstractmethod` to mark methods that subclasses must override. * **Ellipsis**: In Protocols, use `...` instead of a function body to indicate an interface definition. * **Inheritance**: ABCs require `(BaseClass)`, while Protocols generally do not require implementation classes to inherit from them. Practical Examples: Interface Segregation Protocols excel at Interface Segregation. Instead of one giant `Device` interface, you can split requirements based on who uses them. ```python class DiagnosticSource(Protocol): def status_update(self) -> str: ... class ConnectionManager(Protocol): def connect(self) -> None: ... ``` A diagnostic tool only needs to know about `DiagnosticSource`. It doesn't care if the object can connect or disconnect. This limits the knowledge each module needs, making the system easier to maintain. Tips & Gotchas * **Failing Early**: ABCs catch errors at instantiation. Protocols catch them when the object is used or through static analysis. * **Third-Party Code**: Protocols are perfect for third-party libraries. You can define a protocol for a library class you didn't write without modifying its source code. * **Code Reuse**: If you need to share common logic (not just an interface) between subclasses, ABCs are superior because they support standard inheritance and method implementation.
Nominal Typing
Concepts
- Oct 29, 2021