Typing Extensions: Unlocking the Power of Advanced Typing Features in Python
Python has steadily embraced typing features since the release of Python 3.5. However, many valuable typing features introduced in newer Python versions aren’t available in older versions. This is where the typing-extensions
package steps in, providing forward and backward compatibility while unlocking advanced typing capabilities.
In this blog post, we’ll explore dozens of useful APIs provided by typing-extensions
, complete with code snippets and practical examples to illustrate their utility in real-world applications.
Why Use typing-extensions?
The typing-extensions
module makes it possible to use powerful typing features like TypedDict
, Literal
, Protocol
, and more, no matter which Python version you’re using. This package bridges the gap when you’re working on projects requiring compatibility with older Python versions or when you want features from newer PEPs.
Core Features and APIs
1. TypedDict
TypedDict
lets you define dictionaries with a specific set of keys and value types, making your code more explicit and easier to understand.
from typing_extensions import TypedDict class Movie(TypedDict): title: str year: int movie: Movie = {"title": "Inception", "year": 2010}
2. Literal
Literal
allows you to specify that a value must be one of a limited set of possible values.
from typing_extensions import Literal def get_status(flag: Literal['open', 'closed']) -> str: return f"The flag is {flag}" print(get_status("open"))
3. Protocol
Protocol
enables structural subtyping, allowing you to express that a class follows a certain interface without explicit inheritance.
from typing_extensions import Protocol class Flyer(Protocol): def fly(self) -> None: ... class Bird: def fly(self) -> None: print("Flying!") def execute_flight(flyer: Flyer) -> None: flyer.fly() bird = Bird() execute_flight(bird)
4. Final
Final
prevents a variable or method from being overridden.
from typing_extensions import Final PI: Final = 3.14159 class Circle: def __init__(self, radius: float) -> None: self.radius = radius def area(self) -> float: return PI * self.radius ** 2 circle = Circle(5) print(circle.area())
5. Annotated
Annotated
allows you to add metadata to your type hints, useful for validation or documentation purposes.
from typing_extensions import Annotated def process_value(value: Annotated[int, "Must be a positive integer"]) -> int: if value < 0: raise ValueError("Value must be positive!") return value print(process_value(10))
6. Self
Self
simplifies type annotations for methods that return the current instance of a class.
from typing_extensions import Self class Builder: def __init__(self) -> None: self.data = [] def add(self, value: int) -> Self: self.data.append(value) return self builder = Builder().add(10).add(20) print(builder.data)
7. Unpack
Unpack
is used with variadic generics to unpack a sequence of types.
from typing_extensions import Unpack, TypeVarTuple Shape = TypeVarTuple("Shape") def print_shapes(*shapes: Unpack[Shape]) -> None: for shape in shapes: print(shape) print_shapes(1, 2, 3)
Building A Real-World App with typing-extensions
Let’s create a simple library management system demonstrating the above concepts.
from typing_extensions import Literal, TypedDict, Final, Protocol class Book(TypedDict): title: str author: str year: int Mode = Literal['borrow', 'return'] MAX_BOOKS: Final = 5 class Library: def __init__(self) -> None: self.catalog: list[Book] = [] self.borrowed_books: list[Book] = [] def add_book(self, book: Book) -> None: self.catalog.append(book) def update_status(self, book: Book, mode: Mode) -> None: if mode == "borrow": if len(self.borrowed_books) < MAX_BOOKS: self.borrowed_books.append(book) self.catalog.remove(book) else: print("Borrowing limit reached.") elif mode == "return": self.catalog.append(book) self.borrowed_books.remove(book) library = Library() book1 = {"title": "1984", "author": "George Orwell", "year": 1949} library.add_book(book1) library.update_status(book1, "borrow") print("Catalog:", library.catalog) print("Borrowed:", library.borrowed_books)
Conclusion
The typing-extensions
package enhances Python’s static typing ecosystem, providing a wide array of tools to write explicit and robust code. By leveraging these features in your projects, you can improve code readability, maintainability, and reliability.