Robust Software Design with Python Type Hints
Overview
Prerequisites
To follow this guide, you should have a baseline understanding of

Key Libraries & Tools
typing: The standard library module for type hint support.collections.abc: Contains abstract base classes likeIterablefor generic input types.Protocols: Used for structural subtyping (duck typing via hints).
Code Walkthrough: Generic Inputs
When defining function arguments, use the most generic type possible. Using list[float] restricts your function unnecessarily. Instead, use Iterable[float] to allow lists, tuples, or even generators.
from collections.abc import Iterable
def calculate_discount(items: Iterable[float], discount: float) -> list[float]:
return [item * (1 - discount) for item in items]
In this snippet, changing the input to Iterable allows the function to handle a wider range of data structures without modifying the logic. This makes the input "contravariant"—it accepts a wide range of types.
Code Walkthrough: Specific Outputs
While inputs should be generic, outputs must be specific. This ensures the caller knows exactly what methods are available on the returned object. Returning a generic DatabaseConnection protocol hides specific features like start_transaction if that method only exists on a PostgresConnection class.
class PostgresConnection:
def execute(self, query: str): ...
def start_transaction(self): ...
def connect_db() -> PostgresConnection:
return PostgresConnection()
By specifying PostgresConnection as the return type, the IDE provides full autocomplete for all specialized methods, making the output "covariant."
Syntax Notes
Modern Python (3.9+) allows using lowercase list and dict for generics. Python 3.12 introduced a simplified generic syntax for classes and functions, reducing the need for explicit TypeVar declarations in many scenarios.
Practical Examples
- Reporting Systems: Use
Protocolsto define anExporterinterface. This allows addingCSVExporterorJSONExporterclasses without changing the core reporting logic. - Data Processing: Accept an
Iterablefor data streams to process large datasets via generators without loading everything into memory.
Tips & Gotchas
Python ignores type hints at runtime. If you pass a string to a function expecting a float, Python will not stop you unless you use a static type checker like MyPy or the built-in analyzer in your IDE. Always remember: generic for input, specific for output.