Extend Python Typing like a Pro with Typing Extensions

Typing Extensions: A Comprehensive Guide with Examples

The typing-extensions module is an essential library for Python developers who leverage type hinting. It includes backported features from the typing module in newer Python versions and experimental typing functionalities. These features improve code readability, debugging, and development efficiency, especially in larger codebases. In this article, we’ll explore the most useful APIs provided by typing-extensions and how they can make your programming life easier.

Key APIs with Code Examples

1. Annotated

The Annotated API allows decorators or additional metadata to be added to types without affecting the actual utility of these types.

  from typing_extensions import Annotated

  def process_age(age: Annotated[int, "Age in years"]):
      print(f"Processing: {age}")

  process_age(25)

2. Literal

Literal is used to define specific constant values that a variable can accept, enhancing validation and static analysis.

  from typing_extensions import Literal

  def select_mode(mode: Literal["read", "write", "execute"]):
      print(f"Mode selected: {mode}")

  select_mode("read")

3. TypedDict

TypedDict helps in creating dictionary objects with type-annotated keys, ensuring dictionary structures are consistent.

  from typing_extensions import TypedDict

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

  user: User = {"name": "John", "age": 30}
  print(user)

4. ParamSpec

ParamSpec allows dynamic signature specification for higher-order functions or decorators.

  from typing_extensions import ParamSpec, Callable

  P = ParamSpec("P")

  def log_function(func: Callable[P, int]) -> Callable[P, int]:
      def wrapper(*args: P.args, **kwargs: P.kwargs) -> int:
          print("Function called")
          return func(*args, **kwargs)
      return wrapper

  @log_function
  def add(x: int, y: int) -> int:
      return x + y

  print(add(3, 5))

5. Final

The Final decorator ensures a variable or function is immutable and cannot be overridden.

  from typing_extensions import Final

  MAX_USERS: Final = 100

  print(MAX_USERS)

6. Protocol

The Protocol API defines structural subtyping, enabling type checking based on structure rather than inheritance.

  from typing_extensions import Protocol

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

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

  def say_hello(obj: Greetable):
      print(obj.greet())

  p = Person()
  say_hello(p)

Building an App with Typing Extensions

Let’s create a simple app that demonstrates a combination of the features we’ve discussed. This app will define user details, select user permissions, and log events.

  from typing_extensions import Annotated, TypedDict, Literal, Protocol

  class User(TypedDict):
      name: str
      age: int
      role: Literal["admin", "user"]

  class Logger(Protocol):
      def log(self, message: Annotated[str, "Log message"]) -> None:
          ...

  class ConsoleLogger:
      def log(self, message: str) -> None:
          print(f"[LOG]: {message}")

  def process_user(user: User, logger: Logger):
      logger.log(f"Processing user: {user['name']} with role {user['role']}")
      if user["role"] == "admin":
          logger.log(f"Admin-level access granted to {user['name']}")
      else:
          logger.log(f"User-level access granted to {user['name']}")

  user = {"name": "Alice", "age": 25, "role": "admin"}
  logger = ConsoleLogger()
  process_user(user, logger)

Conclusion

The typing-extensions library is a treasure trove for developers who want to bring clarity and power to their Python projects. With features like Annotated, Literal, TypedDict, and more, you can improve the maintainability and robustness of your code. Start incorporating these tools into your workflow today!

Leave a Reply

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