Introduction to Typing Extensions
In the world of Python, type hints have become an invaluable tool for improving code readability and catching bugs early. The typing-extensions
package takes Python’s typing capabilities a step further, offering new runtime features and backward-compatible enhancements from the typing
module. This is especially useful for developers working with older versions of Python that might not support the latest typing features.
Why Use Typing Extensions?
The typing-extensions
library provides many APIs from newer versions of Python for developers who want to use these features in older Python environments. It’s a must-have for writing type-safe and future-proof Python code.
Get Started
To install typing-extensions
, use the following command:
pip install typing-extensions
Key APIs of Typing Extensions
Here’s a look at some of the most useful APIs provided by typing-extensions
, along with examples:
1. Annotated
Add metadata to type hints:
from typing_extensions import Annotated def process_data(data: Annotated[int, "Must be an integer between 1 and 10"]) -> None: print(f"Processing: {data}")
2. Literal
Specifies a limited set of valid values for a variable:
from typing_extensions import Literal def set_light_state(state: Literal["on", "off", "dim"]) -> None: print(f"Light is now: {state}")
3. TypedDict
Used to create dictionaries with fixed key-value pairs:
from typing_extensions import TypedDict class UserInfo(TypedDict): name: str age: int user: UserInfo = {"name": "Alice", "age": 25}
4. Protocol
Defines abstract base classes for structural subtyping:
from typing_extensions import Protocol class SupportsStr(Protocol): def __str__(self) -> str: ... class MyClass: def __str__(self) -> str: return "MyClass instance" obj: SupportsStr = MyClass() print(str(obj))
5. Final
Prevents a class or attribute from being overridden:
from typing_extensions import Final PI: Final = 3.14159 class Base: def method(self) -> None: pass class Derived(Base): def method(self) -> None: ... # Uncommenting the next line will cause a mypy error: # PI = 3.15
6. ParamSpec
Allows flexible function signatures:
from typing_extensions import Callable, ParamSpec P = ParamSpec("P") def log(func: Callable[P, str], *args: P.args, **kwargs: P.kwargs) -> None: print(f"Call with args {args} and kwargs {kwargs}") print("Result:", func(*args, **kwargs)) def greet(name: str) -> str: return f"Hello, {name}" log(greet, "Alice")
Complete Application Example
Let’s build a simple app using typing-extensions
features. This application will log and validate data processing tasks:
from typing_extensions import Literal, Annotated, TypedDict, Protocol, Final # Metadata using Annotated def process_data(data: Annotated[int, "Only numbers between 1 and 100"]) -> None: if not 1 <= data <= 100: raise ValueError("Data out of range") print(f"Processing: {data}") # Fixed states with Literal def set_status(status: Literal["success", "error", "pending"]) -> None: print(f"Status set to: {status}") # TypedDict example class Task(TypedDict): id: int description: str def log_task(task: Task) -> None: print(f"Logging task {task['id']}: {task['description']}") # Protocol example for extendability class Loggable(Protocol): def __str__(self) -> str: ... class CustomTask: def __init__(self, id: int, message: str): self.id = id self.message = message def __str__(self) -> str: return f"CustomTask({self.id}, {self.message})" # Usage process_data(42) set_status("success") task: Task = {"id": 1, "description": "Process user data"} log_task(task) custom_task = CustomTask(2, "Backup data") print(f"Working on: {custom_task}")
Conclusion
The typing-extensions
library is a powerful addition for Python developers who want to write robust, type-safe code compatible with multiple Python versions. Using its functionalities can lead to cleaner, more maintainable, and future-proof Python codebases. Start integrating typing-extensions
in your projects today!