Enhance Python Typing Capabilities with Typing Extensions

Introduction to Typing Extensions

The typing-extensions Python package is a treasure trove of features that extends the built-in typing module. It provides forward-compatible typing hints that work across multiple Python versions, ensuring smoother development experiences. With powerful APIs including custom types, decorators, and more, typing-extensions is indispensable for writing robust and maintainable Python code.

Why Use Typing Extensions?

As Python continues to evolve, new typing features are frequently introduced. However, these enhancements may not be available in older Python versions. The typing-extensions module bridges this gap, offering backwards compatibility for modern typing features.

Dozens of Useful APIs in Typing Extensions

Below, we’ll explore some of the most useful APIs found in typing-extensions, backed by simple examples to brighten your understanding.

1. TypedDict

Defines dictionaries with fixed keys and value types.

  from typing_extensions import TypedDict

  class User(TypedDict):
      id: int
      name: str

  user: User = {"id": 1, "name": "Alice"}
  print(user)

2. Literal

Restricts a value to a set of literals.

  from typing_extensions import Literal

  def get_status(code: Literal["success", "failure"]):
      print(f"Status: {code}")

  get_status("success")

3. Final

Marks variables or attributes as immutable.

  from typing_extensions import Final

  PI: Final = 3.14159
  print(f"PI is {PI}")

4. Concatenate

Supports combining types for callable annotations, particularly useful for decorators.

  from typing_extensions import Concatenate, Callable
  from typing import TypeVar

  T = TypeVar("T")

  def log_args(
      func: Callable[Concatenate[int, str, T], None]
  ) -> Callable[Concatenate[int, str, T], None]:
      def wrapper(x: int, y: str, *args, **kwargs):
          print(f"Arguments: {x}, {y}")
          return func(x, y, *args, **kwargs)
      return wrapper

  @log_args
  def greet(age: int, name: str) -> None:
      print(f"Hello {name}, you are {age} years old!")

  greet(25, "Alice")

5. Protocol

Defines structural subtyping to enforce method signatures.

  from typing_extensions import Protocol

  class Greetable(Protocol):
      def greet(self) -> None:
          ...

  class Person:
      def greet(self) -> None:
          print("Hello!")

  def welcome(person: Greetable):
      person.greet()

  welcome(Person())

6. Self

Used to annotate methods that return self instances.

  from typing_extensions import Self

  class Fluent:
      def set_value(self, value: int) -> Self:
          self.value = value
          return self

  f = Fluent().set_value(10)
  print(f.value)

7. override

Provides an explicit marker for methods meant to override a base class method.

  from typing_extensions import override

  class Base:
      def say_hello(self):
          print("Hello from Base")

  class Sub(Base):
      @override
      def say_hello(self):
          print("Hello from Sub")

  Sub().say_hello()

Example Application Using Typing Extensions

Let’s build a small application utilizing multiple features from typing-extensions.

  from typing_extensions import TypedDict, Protocol, Literal, Final, Self

  # TypedDict for structured data
  class Product(TypedDict):
      id: int
      name: str
      price: float

  # Protocol for enforcing class behaviors
  class Calculator(Protocol):
      def calculate_total(self, quantity: int) -> float:
          ...

  # Product implementation
  class Item:
      TAX_RATE: Final = 0.07

      def __init__(self, details: Product):
          self.details = details

      def calculate_total(self, quantity: int) -> float:
          subtotal = self.details["price"] * quantity
          tax = subtotal * self.TAX_RATE
          return subtotal + tax

      def set_discount(self, discount: float) -> Self:
          self.discount = discount
          return self

  # Example usage
  product = {"id": 1, "name": "Laptop", "price": 1200.00}
  item = Item(product)
  total = item.set_discount(0.10).calculate_total(2)
  print(f"Total cost: ${total:.2f}")

Conclusion

The typing-extensions library is a powerful tool for developers who rely on type hints in Python to write more maintainable and robust code. From TypedDict and Literal to Protocol and Self, the APIs it offers are diverse and highly practical. Whether you’re building data-intensive applications or reusable libraries, knowing typing-extensions gives you a competitive edge. Try it out in your next project!

Leave a Reply

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