Description
Introduction
Python's type hinting system, introduced in PEP 484 and expanded in subsequent PEPs, has become a powerful tool for static type checking and improving code readability. However, one feature that is currently missing is intersection types, which allow specifying that a value must satisfy multiple types or protocols simultaneously. This feature is particularly useful for expressing complex constraints, such as requiring an object to implement multiple interfaces (similar to Rust's trait bounds).
This proposal suggests adding support for the &
operator in Python's type hints to represent intersection types.
Motivation
-
Expressing Multiple Constraints:
- Currently, Python lacks a concise way to specify that a value must satisfy multiple type constraints. For example, if a function requires an object that implements both
Speak
andWalk
protocols, there is no straightforward way to express this in type hints. - Workarounds like
Union[Type1, Type2]
orType1
with runtime checks are either incorrect or cumbersome.
- Currently, Python lacks a concise way to specify that a value must satisfy multiple type constraints. For example, if a function requires an object that implements both
-
Alignment with Other Languages:
- Many statically typed languages (e.g., TypeScript, Rust, Scala) support intersection types or similar constructs. Adding this feature would bring Python's type system closer to these languages, making it easier for developers to transition between them.
-
Improved Static Analysis:
- Intersection types would enable static type checkers (e.g.,
mypy
,pyright
) to perform more precise type inference and validation, reducing the likelihood of runtime errors.
- Intersection types would enable static type checkers (e.g.,
-
Better Code Documentation:
- Intersection types would make it easier to document complex requirements in function signatures, improving code readability and maintainability.
Proposed Syntax
The &
operator would be used to denote intersection types in type hints. For example:
def perform_actions(animal: Speak & Walk) -> None:
animal.speak()
animal.walk()
Here, Speak & Walk
indicates that the animal
parameter must satisfy both the Speak
and Walk
protocols.
Semantics
-
Compatibility:
- An object is considered compatible with an intersection type
A & B
if it satisfies bothA
andB
. For example:from abc import ABC, abstractmethod # Define "traits" as abstract base classes class Speak(ABC): @abstractmethod def speak(self): pass class Walk(ABC): @abstractmethod def walk(self): pass # Implement the "traits" in a class class Dog(Speak, Walk): def speak(self): print("Woof!") def walk(self): print("The dog is walking.") # Function that requires an object implementing both "traits" def perform_actions(animal: Speak & Walk): animal.speak() animal.walk() # Usage my_dog = Dog() perform_actions(my_dog)
- An object is considered compatible with an intersection type
-
Static Type Checking:
- Static type checkers would validate that the object passed to a function with an intersection type hint satisfies all the required types.
-
Runtime Behavior:
- Intersection types would have no runtime impact. They are purely for static type checking and documentation.
Backward Compatibility
- The addition of the
&
operator for intersection types would be fully backward compatible. Existing code that does not use this feature would remain unaffected.