Python’s flexibility and ease of use have made it a favorite among developers. With the evolution of type annotations, the typing
module has become indispensable. However, to stay compatible with older Python versions and to introduce experimental typing features, the typing-extensions
package comes into play. In this guide, we’ll delve into typing-extensions
, explore its APIs, and showcase examples to help you use it effectively in your projects.
What is typing-extensions?
The typing-extensions
package is a supplementary library that provides backports of type hints and utilities introduced in newer Python versions. It ensures that codebases requiring forward compatibility can leverage these modern features, even on lower Python versions.
Why Use typing-extensions?
- Access to experimental and upcoming type utilities.
- Compatibility with older Python versions.
- Enhanced flexibility when working with type annotations in Python projects.
Key APIs and Usage Examples
Let’s explore some of the most commonly used APIs provided by typing-extensions
:
1. Literal
Define specific, finite constant values that a variable can accept.
from typing_extensions import Literal def get_status(status: Literal['open', 'closed', 'in-progress']) -> str: return f"Status is {status}" print(get_status("open")) # Valid # print(get_status("done")) # Invalid, as it's not part of the Literal type
2. TypedDict
Create a dictionary with a fixed schema for better type checking and clarity.
from typing_extensions import TypedDict class User(TypedDict): name: str age: int user: User = {"name": "Alice", "age": 30} print(user)
3. Final
Mark a variable or method as immutable or non-overridable.
from typing_extensions import Final PI: Final = 3.14159 # PI = 3.14 # Error: Cannot reassign to a Final variable
4. Protocol
Define structural subtyping or duck typing for your classes.
from typing_extensions import Protocol class Walkable(Protocol): def walk(self) -> None: ... class Person: def walk(self) -> None: print("Person is walking") def move(entity: Walkable) -> None: entity.walk() p = Person() move(p) # Valid since Person implements the Walkable protocol
5. Required
and NotRequired
Fine-tune fields in a TypedDict
to be explicitly required or optional.
from typing_extensions import TypedDict, Required, NotRequired class Config(TypedDict): debug: Required[bool] logging_level: NotRequired[str] config: Config = {"debug": True} print(config)
6. Self
Refer to the current class instance in its own methods explicitly.
from typing_extensions import Self class Node: def set_value(self, value: int) -> Self: self.value = value return self
A Real-World Application
Here’s an example of using some of the above APIs to create a simple task management system:
from typing_extensions import Literal, TypedDict, Protocol, Final class Task(TypedDict): id: int title: str status: Literal['pending', 'in-progress', 'completed'] class TaskManager(Protocol): def add_task(self, task: Task) -> None: ... def complete_task(self, task_id: int) -> None: ... class SimpleTaskManager: TASK_STATUSES: Final = ['pending', 'in-progress', 'completed'] def __init__(self): self.tasks: list[Task] = [] def add_task(self, task: Task) -> None: self.tasks.append(task) def complete_task(self, task_id: int) -> None: for task in self.tasks: if task['id'] == task_id: task['status'] = 'completed' manager = SimpleTaskManager() manager.add_task({"id": 1, "title": "Learn typing-extensions", "status": "pending"}) manager.complete_task(1) print(manager.tasks)
Conclusion
typing-extensions
simplifies type hinting for developers, making Python code robust and maintainable. Whether you’re building small apps or enterprise applications, understanding and utilizing these features prepares your code for the future while maintaining backward compatibility.