Introduction to Typing Extensions
The typing-extensions module in Python provides a suite of supplementary type hinting tools that enhance Python’s standard typing
module. By using typing-extensions
, you can access new features of type hints and future-proof your code for upcoming Python releases. This module is extremely useful for developers who aim to create robust, type-safe, and maintainable applications.
Why Typing Extensions?
While Python’s built-in typing
module offers a variety of utilities for static typing, it does not contain newer features until you upgrade to the latest version of Python. This is where typing-extensions
excels by backporting these features to older Python versions. It allows your code to remain compatible with future versions of Python while preserving backward compatibility.
Dozens of Useful APIs in Typing Extensions
Below are some of the most commonly used APIs from typing-extensions
, along with code snippets to help you understand their utility:
1. TypedDict
TypedDict
allows you to define dictionary types with a fixed set of keys. This is useful when you need type checking for dictionary objects.
from typing_extensions import TypedDict class User(TypedDict): id: int name: str active: bool user_info: User = {"id": 1, "name": "Alice", "active": True}
2. Protocol
Protocol
allows you to define structural subtyping, also known as “duck typing.”
from typing_extensions import Protocol class Greeter(Protocol): def greet(self, name: str) -> str: pass class FormalGreeter: def greet(self, name: str) -> str: return f"Good day, {name}!" def welcome_user(g: Greeter, username: str): print(g.greet(username)) welcome_user(FormalGreeter(), "Alice")
3. Literal
The Literal
type allows you to specify that a variable should take one specific value from a pre-defined set.
from typing_extensions import Literal def fetch_data(mode: Literal["fast", "slow"]) -> None: if mode == "fast": print("Fetching data quickly...") elif mode == "slow": print("Fetching data slowly...") fetch_data("fast")
4. Final
Use Final
to define constants that should not be reassigned.
from typing_extensions import Final PI: Final = 3.14159 # PI = 3.14 # This will raise a type checker error
5. @runtime_checkable
The @runtime_checkable
decorator allows protocols to be checked at runtime using isinstance
.
from typing_extensions import Protocol, runtime_checkable @runtime_checkable class Runnable(Protocol): def run(self) -> None: pass class Car: def run(self) -> None: print("The car is running!") assert isinstance(Car(), Runnable)
Application Example
Let’s build an app simulation using typing-extensions
that leverages the above APIs.
from typing_extensions import TypedDict, Protocol, Literal class Item(TypedDict): id: int name: str price: float class DiscountStrategy(Protocol): def apply_discount(self, items: list[Item]) -> float: pass class PercentageDiscount: def __init__(self, discount: float): self.discount = discount def apply_discount(self, items: list[Item]) -> float: total = sum(item["price"] for item in items) return total * (1 - self.discount) def checkout(strategy: DiscountStrategy, items: list[Item], mode: Literal["online", "offline"]): total = strategy.apply_discount(items) print(f"Checkout mode: {mode.title()}, Total: ${total:.2f}") cart = [ {"id": 1, "name": "Laptop", "price": 1000.0}, {"id": 2, "name": "Mouse", "price": 50.0}, ] discount_strategy = PercentageDiscount(0.1) checkout(discount_strategy, cart, "online")
Conclusion
typing-extensions
is a vital tool for developers who rely on static typing to ensure code quality and maintainability. It offers powerful and flexible features like TypedDict
, Protocol
, Literal
, and more. By integrating these into your code, you can achieve better type safety and clarity without sacrificing compatibility between Python versions.