A Comprehensive Guide to Typing Extensions for Enhanced Python Typing

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.

Leave a Reply

Your email address will not be published. Required fields are marked *