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
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
Cons
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.