Introduction to typing-extensions
The typing-extensions
library is the go-to package for Python developers who want to experiment with typing features not yet available in the standard typing
module. It provides compatibility for older Python versions and serves as a testing ground for new type hints before they get integrated into the standard library. Let’s dive deep into this useful library and understand how to leverage its features in your Python projects.
Why Use typing-extensions?
While the typing
module is continually evolving, not all the updated typing features make it to earlier Python versions. typing-extensions
ensures backward compatibility and allows experimentation with future typing constructs. Below, we explore its most notable APIs through examples.
Dozens of Useful APIs in typing-extensions
1. Literal
The Literal
type is used to allow specific constant values. This is useful in functions where the argument is expected to be from a predefined set of values.
from typing_extensions import Literal def greet(language: Literal['en', 'es', 'fr']) -> str: translations = {'en': 'Hello', 'es': 'Hola', 'fr': 'Bonjour'} return translations[language] print(greet('en')) # Output: Hello
2. TypedDict
TypedDict
enables defining dictionaries with specific types for keys and values.
from typing_extensions import TypedDict class User(TypedDict): name: str age: int user: User = {'name': 'Alice', 'age': 30} print(user) # Output: {'name': 'Alice', 'age': 30}
3. Final
Final
enforces immutability by indicating that the variable or method cannot be overridden.
from typing_extensions import Final PI: Final = 3.14159 # PI = 3.14 # This will raise a mypy error
4. Protocol
Protocol
is used for structural subtyping, allowing you to specify required methods and properties for a type.
from typing_extensions import Protocol class Greeter(Protocol): def greet(self) -> None: ... class Person: def greet(self) -> None: print("Hello!") def say_hello(greeter: Greeter) -> None: greeter.greet() person = Person() say_hello(person) # Output: Hello!
5. Annotated
Annotated
can be used for adding metadata to types.
from typing_extensions import Annotated def process_data(data: Annotated[str, "Input must be a string"]) -> str: return data.upper() print(process_data("example")) # Output: EXAMPLE
6. Self
Self
is a convenient way to annotate methods that return the same instance type.
from typing_extensions import Self class FluentBuilder: def __init__(self): self.result = "" def add(self, text: str) -> Self: self.result += text return self def build(self) -> str: return self.result builder = FluentBuilder() print(builder.add("Hello ").add("World!").build()) # Output: Hello World!
7. Unpack
Unpack is used for unpacking tuple or list types into distinct variables in type hints.
from typing_extensions import Unpack from typing import TypedDict class Point(TypedDict): x: int y: int def print_point(**point: Unpack[Point]) -> None: print(f"x: {point['x']}, y: {point['y']}") print_point(x=10, y=20) # Output: x: 10, y: 20
8. @runtime_checkable
Allows Protocol
to be used in runtime type checking with isinstance
or issubclass
.
from typing_extensions import Protocol, runtime_checkable @runtime_checkable class Movable(Protocol): def move(self) -> None: ... class Car: def move(self) -> None: print("The car is moving") car = Car() if isinstance(car, Movable): car.move() # Output: The car is moving
Building an App Using typing-extensions APIs
Here’s an example of a basic task management app that leverages TypedDict
, Literal
, and Annotated
.
from typing_extensions import TypedDict, Literal, Annotated from datetime import datetime class Task(TypedDict): id: int title: str status: Literal['todo', 'in-progress', 'done'] created_at: Annotated[datetime, "Creation timestamp"] tasks: list[Task] = [] def add_task(title: str, status: Literal['todo', 'in-progress', 'done'] = 'todo') -> None: task: Task = { 'id': len(tasks) + 1, 'title': title, 'status': status, 'created_at': datetime.now() } tasks.append(task) def list_tasks() -> None: for task in tasks: print(f"{task['id']} - {task['title']} ({task['status']})") add_task("Write blog post", "todo") add_task("Review PR", "in-progress") list_tasks() # Output: # 1 - Write blog post (todo) # 2 - Review PR (in-progress)
Conclusion
The typing-extensions
library offers powerful tools to augment your Python development, especially when you require features not yet integrated into the default typing module. With its compatibility and flexibility, it ensures your codebase remains robust as Python evolves. Start integrating these features today!