Overview Writing Python code is easy, but maintaining it as it grows is a different beast entirely. Without a clear architectural strategy, projects quickly devolve into "spaghetti code"—a mess of tight coupling, circular imports, and fragile dependencies. This tutorial demonstrates how to use **abstraction** to decouple your code, specifically focusing on how to transition from concrete implementations to flexible contracts. By shifting focus from *what* a specific class is to *how* it should behave, you create a system that is easier to test, extend, and understand. We will explore three primary ways to implement these contracts: Abstract Base Classes (ABCs), Protocols, and Callables. Prerequisites To follow this guide, you should have a solid grasp of Python fundamentals, including classes, functions, and basic type hinting. Familiarity with the Pillow library for image processing and the concept of dependency injection will help you grasp the architectural shifts being made. Key Libraries & Tools * **abc**: The built-in module for defining Abstract Base Classes. * **typing**: Contains `Protocol` for structural typing and `Callable` for functional abstractions. * **functools**: Specifically the `partial` function for partial argument application. * **Pillow (PIL)**: Used for the underlying image manipulation tasks. Code Walkthrough The Problem: Concrete Coupling In the original "spaghetti" version, the processing function explicitly checks the type of each filter and applies settings based on that type. This requires importing every specific filter class into the processing module. Solution 1: Abstract Base Classes (ABCs) By defining a base contract, we ensure every filter implements an `apply` method. This allows the processor to treat any filter the same way. ```python from abc import ABC, abstractmethod from PIL import Image class FilterBase(ABC): @property @abstractmethod def name(self) -> str: pass @abstractmethod def apply(self, image: Image.Image) -> Image.Image: pass ``` Solution 2: Protocols (Structural Typing) Protocols allow for "duck typing" with static type safety. Unlike ABCs, your filter classes don't need to inherit from the protocol; they just need to have the matching methods. ```python from typing import Protocol class Filter(Protocol): def apply(self, image: Image.Image) -> Image.Image: ... ``` Solution 3: Callables and Functional Design Sometimes a class is overkill. We can represent a filter as a simple `Callable` that takes an image and returns an image. To handle filters that need configuration (like intensity), we use closures or `functools.partial`. ```python from functools import partial from typing import Callable ImageFilter = Callable[[Image.Image], Image.Image] def apply_grayscale(image: Image.Image, intensity: float) -> Image.Image: # implementation logic return image.convert("L") Create a configured filter function grayscale_filter = partial(apply_grayscale, intensity=0.5) ``` Syntax Notes When using `Protocol`, the `...` (ellipsis) is the standard way to indicate a method body that exists only for type checking. For `Callable`, the syntax `Callable[[Arg1Type, Arg2Type], ReturnType]` provides precise hints for higher-order functions. Practical Examples This pattern is essential in plugin architectures. For instance, if you are building a data export tool, you can define an `Exporter` Protocol. Whether you add CSV, JSON, or SQL exports later, your main logic remains untouched because it only interacts with the abstraction. Tips & Gotchas Avoid over-engineering; if you only have two filters that never change, abstractions might be unnecessary. Beware that `functools.partial` objects do not carry the `__name__` attribute of the original function, which can break logging or debugging tools that rely on function names. Always designate a "dirty corner" in your code—usually the `main.py` file—where concrete instances are actually created and wired together.
Protocols
Products
TL;DR
ArjanCodes (3 mentions) highlights Protocols in Python for defining component behavior and preventing "God Object" anti-patterns, as seen in "Stop Hardcoding Everything: Use Dependency Injection" and "Protocol Or ABC In Python - When to Use Which One?".
- Jul 4, 2025
- Dec 20, 2024
- Oct 29, 2021