Introduction to Typing Extensions
Python’s type hinting feature has significantly improved code readability and maintainability. However, the standard library’s typing
module does not always keep up with newer Python versions or edge-case typing requirements. This is where the typing-extensions
package comes into play. It serves as a backport and a proving ground for new type hinting features that might eventually make their way into the standard library.
Let’s dive into the functionality of typing-extensions
and explore how to utilize its wide range of APIs to write flexible, maintainable, and type-safe Python code. Additionally, we will demonstrate how to use these APIs in an application example.
Key Features and APIs of Typing Extensions with Examples
1. Annotated
The Annotated
type allows you to add metadata to type hints. This is useful for validations, documentation, or library-specific extensions.
from typing import Annotated from typing_extensions import Annotated def process_data(data: Annotated[int, "must be a positive integer"]) -> str: return f"Processed: {data}" process_data(42) # Works fine # Metadata is ignored by Python but can be used by external tools
2. Literal
Use Literal
to define specific constant values allowed for a type.
from typing_extensions import Literal def set_status(status: Literal["active", "inactive", "pending"]) -> None: print(f"Status set to {status}") set_status("active") # Valid set_status("paused") # Type checker will raise an error
3. TypedDict
The TypedDict
lets you define type hints for dictionaries with specific key-value pairs.
from typing_extensions import TypedDict class User(TypedDict): id: int name: str is_active: bool def create_user(user: User) -> None: print(f"User created: {user}") user = {"id": 1, "name": "Alice", "is_active": True} create_user(user) # Valid
4. Final
The Final
qualifier is used to declare constants or immutable variables that should not be overridden or reassigned.
from typing_extensions import Final DATABASE_URL: Final = "postgresql://localhost:5432/mydb" DATABASE_URL = "sqlite://:memory:" # Type checkers will raise an error
5. Self
Use Self
to indicate that a method returns an instance of its class.
from typing_extensions import Self class Builder: def add_step(self) -> Self: print("Step added") return self def build(self) -> None: print("Build complete") builder = Builder() builder.add_step().build()
6. Concatenate
Advanced type hinting for callable arguments using Concatenate
.
from typing_extensions import Concatenate, Callable, TypeVar T = TypeVar("T") def execute(func: Callable[Concatenate[str, int, T], None], name: str, count: int, extra: T) -> None: func(name, count, extra) def log_message(name: str, count: int, extra: dict) -> None: print(f"{name} - {count}: {extra}") execute(log_message, "Event", 5, {"info": "task started"})
7. NotRequired
and Required
Control optionality of keys in TypedDict
using NotRequired
and Required
.
from typing_extensions import TypedDict, NotRequired class Config(TypedDict): host: str port: int debug: NotRequired[bool] config: Config = {"host": "localhost", "port": 8080}
Application Example Using Typing Extensions
Here is a sample application that demonstrates a combination of various typing-extensions
APIs.
from typing_extensions import Annotated, TypedDict, Literal, Final, Self, NotRequired # Defining constants API_ENDPOINT: Final = "https://api.example.com" # Using Annotated for metadata class Item(TypedDict): id: Annotated[int, "Unique identifier"] name: str status: Literal["active", "inactive"] price: Annotated[float, "Price in USD"] discount: NotRequired[float] class Cart: def __init__(self) -> None: self.items: list[Item] = [] def add_item(self, item: Item) -> Self: self.items.append(item) print(f"Added item: {item}") return self def get_total(self) -> float: return sum(item["price"] - (item.get("discount", 0.0) or 0.0) for item in self.items) # Building the application cart = Cart() cart.add_item({"id": 1, "name": "Laptop", "status": "active", "price": 999.99}) \ .add_item({"id": 2, "name": "Mouse", "status": "active", "price": 49.99, "discount": 10.0}) print(f"Cart total: ${cart.get_total():.2f}")
Conclusion
The typing-extensions
package is indispensable for developers building applications with robust type hinting. It enables you to stay ahead by leveraging advanced type hints and gradually adopting new type annotations. Whether you’re developing APIs, processing data, or building scalable applications, typing-extensions
is a valuable tool to enhance your codebase.