Discover Typing Extensions A Comprehensive Guide with Examples

Introduction to Typing-Extensions

The typing-extensions package is a must-have library for Python developers seeking to leverage type annotations and maintain compatibility across different Python versions. It provides backports of new type-hinting features introduced in newer versions of the typing module, ensuring that your code remains forward-compatible. This blog post will explore various APIs provided by typing-extensions with practical code examples and conclude with a simple app that uses multiple introduced APIs.

Why Typing-Extensions?

The Python typing module evolves rapidly, and the typing-extensions package serves as a bridge for developers working with older Python versions. It allows access to cutting-edge type-checking features such as TypedDict, Protocol, Literal, and more, regardless of your Python runtime.

Useful APIs in Typing-Extensions

1. Literal

The Literal type annotation allows you to specify a restricted set of constant values that a variable or function argument can accept.

  from typing_extensions import Literal

  def order_status(status: Literal["pending", "shipped", "delivered"]) -> str:
      return f"Your order is {status}."

  print(order_status("pending"))  # Valid
  # print(order_status("cancelled"))  # Type checker will flag this as invalid

2. TypedDict

The TypedDict class allows you to define dictionaries with a specific key-value structure, making them great for specifying JSON-like objects.

  from typing_extensions import TypedDict

  class Product(TypedDict):
      id: int
      name: str
      price: float

  product: Product = {"id": 1, "name": "Laptop", "price": 999.99}
  print(product)

3. Protocol

The Protocol class provides structural subtyping (also known as “duck typing”), enabling you to specify that a class must implement certain methods or attributes.

  from typing_extensions import Protocol

  class Greeter(Protocol):
      def greet(self) -> str:
          ...

  class FriendlyGreeter:
      def greet(self) -> str:
          return "Hello!"

  def introduce(greeter: Greeter):
      print(greeter.greet())

  friendly = FriendlyGreeter()
  introduce(friendly)

4. Final

Use Final to declare a variable or method that cannot be overridden or reassigned.

  from typing_extensions import Final

  MAX_USERS: Final = 100
  
  # MAX_USERS = 200  # This will raise an error in type-checking

5. Concatenate and ParamSpec

These are advanced tools for defining flexible higher-order functions with accurate type hints.

  from typing_extensions import Concatenate, ParamSpec
  from typing import Callable

  P = ParamSpec("P")

  def add_logging(f: Callable[Concatenate[str, P], str]) -> Callable[P, str]:
      def wrapper(*args: P.args, **kwargs: P.kwargs) -> str:
          print("Logging: Called with", args, kwargs)
          return f("INFO", *args, **kwargs)
      return wrapper

  def process_data(level: str, value: int) -> str:
      return f"{level}: Processed {value}"

  logged_process = add_logging(process_data)
  print(logged_process(42))

6. Self

The Self annotation improves type annotations for methods that return an instance of the same class.

  from typing_extensions import Self

  class Builder:
      def set_value(self, value: str) -> Self:
          self.value = value
          return self

  builder = Builder().set_value("Demo")
  

7. Annotated

Use Annotated to attach metadata to type hints, often used in frameworks for validation or documentation.

  from typing_extensions import Annotated
  from typing import Any

  def process(value: Annotated[str, "Must be a non-empty string"]) -> None:
      if not value:
          raise ValueError("Invalid input")
      print(value)

  process("Valid Input")

Example Application

Using multiple APIs introduced above, let’s create a type-safe Order Management System.

  from typing_extensions import Literal, TypedDict, Protocol

  class Product(TypedDict):
      id: int
      name: str
      price: float

  def display_status(status: Literal["pending", "shipped", "delivered"]) -> str:
      return f"Order Status: {status}"

  class Greeter(Protocol):
      def greet(self) -> str:
          ...

  class FriendlyGreeter:
      def greet(self) -> str:
          return "Welcome to the Order Management System!"

  # Sample Usage
  product = {"id": 1, "name": "Laptop", "price": 999.99}
  greeter = FriendlyGreeter()

  print(greeter.greet())
  print(display_status("pending"))
  print(f"Product Details: {product}")

Conclusion

The typing-extensions package is an invaluable tool for Python developers, making it easier to write robust and maintainable code with advanced type hinting capabilities. Whether you’re developing libraries, APIs, or applications, leveraging typing-extensions ensures forward-compatibility and improved code quality.

Leave a Reply

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