Skip to content

Proposal: Adding Intersection Types (&) to Python Type Hints #1906

Closed as not planned
@kamalfarahani

Description

@kamalfarahani

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

  1. 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 and Walk protocols, there is no straightforward way to express this in type hints.
    • Workarounds like Union[Type1, Type2] or Type1 with runtime checks are either incorrect or cumbersome.
  2. 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.
  3. 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.
  4. 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

  1. Compatibility:

    • An object is considered compatible with an intersection type A & B if it satisfies both A and B. 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)
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: featureDiscussions about new features for Python's type annotations

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions