Overview of Abstraction Layers Defining interfaces allows developers to create a contract between different parts of a software system. In Python, we primarily use Abstract Base Classes (ABCs) and Protocols to achieve this. These tools enable you to write code that depends on behaviors rather than specific implementations, facilitating cleaner Dependency Inversion. While both define what a class should do, they differ significantly in how they enforce these rules—one through explicit inheritance and the other through structural typing. Prerequisites To get the most out of this guide, you should understand Python fundamentals, including classes and methods. Familiarity with type hinting and basic object-oriented programming (OOP) concepts like inheritance will help you navigate the differences between nominal and structural typing. Key Libraries & Tools * **abc**: The standard library module providing the `ABC` class and `abstractmethod` decorator for defining formal interfaces. * **typing**: Contains the `Protocol` class and the `runtime_checkable` decorator used for structural typing and runtime validation. * **Pylance/Mypy**: Essential static analysis tools that help catch interface mismatches before you ever run your code. Code Walkthrough: Implementing ABCs Abstract Base Classes use nominal typing. You must explicitly inherit from the base class to satisfy the interface. This is perfect when you want to provide a base implementation to avoid code duplication. ```python from abc import ABC, abstractmethod class SerializedFileHandler(ABC): def __init__(self, filename: str): self.filename = filename @abstractmethod def serialize(self, data: dict) -> bytes: """Must be implemented by sub-classes.""" pass def write(self, data: dict): # Shared logic that uses the abstract method content = self.serialize(data) with open(self.filename, 'wb') as f: f.write(content) ``` In this snippet, `write` is a concrete method. Any child class, like a `JsonHandler`, only needs to implement `serialize`. The ABC handles the file I/O logic, keeping your sub-classes lean. Syntax Notes: The Power of Protocols Protocols (introduced in Python 3.8) implement "duck typing." An object satisfies a Protocol simply by having the required methods, even without inheritance. ```python from typing import Protocol, runtime_checkable @runtime_checkable class Writable(Protocol): def write(self, data: dict) -> None: ... ``` The `...` (ellipsis) is standard syntax for protocol methods. The `@runtime_checkable` decorator is vital if you intend to use `isinstance()` checks later; without it, Python will throw an error because Protocols are normally for static type checkers only. Practical Examples Use Protocols when working with third-party libraries. If a library returns an object with a `write()` method, you can define a `Writable` Protocol in your own code to type-hint that object. You cannot make that third-party class inherit from your ABC, making Protocols the only viable path for strict typing. Tips & Gotchas Don't rely solely on runtime checks for Protocols. The Python interpreter often performs shallow checks. For instance, it might verify a method exists but ignore signature mismatches (like expecting bytes but receiving a dictionary). Always pair these abstractions with a static type checker like Mypy to ensure your data flows correctly through the interface.
Abstract Base Classes
Concepts
- Mar 29, 2024
- Sep 10, 2021