'How to specify that a python object must be two types at once

I'm using the Python typing module throughout my project, and I was wondering if there was a way to specify that a given object must be of two different types, at once. This most obviously arises when you have specified two protocols, and expect a single object to fulfil both:

class ReturnsNumbers(Protocol):
    def get_number(self) -> Int:
        pass

class ReturnsLetters(Protocol):
    def get_letter(self) -> str:
        pass

def get_number_and_letter(x: <what should this be?>) -> None:
    print(x.get_number(), x.get_letter())

Thanks in advance!



Solution 1:[1]

Create a new type that inherits from all types you wish to combine, as well as Protocol.

class ReturnsNumbersAndLetters(ReturnsNumbers, ReturnsLetters, Protocol):
    pass

def get_number_and_letter(x: ReturnsNumbersAndLetters) -> None:
    print(x.get_number(), x.get_letter())

Solution 2:[2]

Per this comment on the relevant issue, you can make a simple intersection protocol:

class ReturnsNumbersAndLetters(ReturnsNumbers, ReturnsLetters, Protocol):
    pass

then use it instead:

def get_number_and_letter(x: ReturnsNumbersAndLetters) -> None:
    print(x.get_number(), x.get_letter())

Solution 3:[3]

I would say that the other solutions presented have merit except you may have to deal with the case where you have two separate objects that both implement both classes. If you know for sure that the object you want inherits from both ReturnsNumbers and ReturnsLetters, you could do this:

T = TypeVar(bound = ReturnsNumbers|ReturnsLetters)

def get_number_and_letter(x: T) -> None:
    print(x.get_number(), x.get_letter())

This avoids the explicit definition of a (possibly) useless class while ensuring that you get the type-hinting that you want.

You can find the relevant documentation here.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Amadan
Solution 3