Python > Advanced Python Concepts > Type Hinting > Static Type Checkers (e.g., MyPy)

Advanced Type Hinting: Generics and Protocols

This snippet showcases advanced type hinting features like Generics and Protocols in Python. Generics allow you to write code that works with different types while maintaining type safety. Protocols define shared behavior using structural typing.

Introduction to Generics

Generics allow you to write functions and classes that work with a variety of types without sacrificing type safety. They are particularly useful for collections and data structures.

Generic Function Example

In this example, TypeVar('T') defines a type variable T. The first_element function takes a list of type T and returns an element of type T. MyPy ensures that the return type matches the type of the list elements.

from typing import TypeVar, List

T = TypeVar('T')

def first_element(items: List[T]) -> T:
    return items[0]

numbers: List[int] = [1, 2, 3]
first_number: int = first_element(numbers)
print(first_number)

strings: List[str] = ["a", "b", "c"]
first_string: str = first_element(strings)
print(first_string)

Introduction to Protocols

Protocols define shared behavior using structural typing (also known as duck typing). A class that implements the methods specified in a protocol is considered a subtype of that protocol, regardless of its inheritance hierarchy.

Protocol Example

The SupportsClose protocol defines a close method. Both the File and Socket classes implement the close method, so they are considered subtypes of SupportsClose. The close_all function accepts a list of SupportsClose objects and calls the close method on each object.

from typing import Protocol

class SupportsClose(Protocol):
    def close(self) -> None:
        ...

def close_all(things: list[SupportsClose]) -> None:
    for thing in things:
        thing.close()

class File:
    def close(self) -> None:
        print("Closing file")

class Socket:
    def close(self) -> None:
        print("Closing socket")

files = [File(), Socket()]
close_all(files)

Concepts Behind the Snippet

This snippet illustrates how generics and protocols can be used to write more flexible and reusable code while maintaining type safety. Generics allow you to work with different types of data, while protocols define shared behavior based on structural typing.

Real-Life Use Case

Generics are commonly used in data structures like lists, dictionaries, and sets. Protocols are useful for defining interfaces for interacting with different types of objects, such as file-like objects or network connections.

Best Practices

  • Use generics when writing functions or classes that need to work with multiple types.
  • Use protocols when defining interfaces for interacting with different types of objects.
  • Consider using TypeVar with bounds or constraints to restrict the types that can be used with a generic function or class.

Interview Tip

Understand the differences between generics and protocols and when to use each. Be prepared to explain how they improve code flexibility and type safety.

When to Use Them

Use generics and protocols when you need to write code that is both type-safe and reusable. They are particularly useful in libraries and frameworks that need to support a wide range of types.

Alternatives

Alternatives to generics include using Any type (which disables type checking) or using multiple function overloads (which can be cumbersome). Alternatives to protocols include using abstract base classes (which require inheritance) or relying on runtime duck typing (which can lead to errors).

Pros

  • Improved code flexibility
  • Enhanced type safety
  • Increased code reusability
  • Better code maintainability

Cons

  • Generics and protocols can be more complex to understand than simple type hints.
  • Using generics and protocols can sometimes make the code more verbose.

FAQ

  • What is the difference between a protocol and an abstract base class?

    Protocols use structural typing, meaning that a class is considered a subtype of a protocol if it implements the required methods, regardless of its inheritance hierarchy. Abstract base classes require explicit inheritance.

  • Can I use generics with classes?

    Yes, you can use generics with classes. For example, you can define a generic list class that can hold elements of any type.

  • Are protocols only useful for defining interfaces?

    Protocols are primarily used for defining interfaces, but they can also be used to define constraints on the types of objects that can be used in a function or class.