python - specify type with multiple bases (typing AND operator)

Viewed 1249

I understand (as explained in this question and the docs) that a type hint of X or Y can be expressed as:

Union[X,Y]

But how does one express a type hint of X and Y? This would be useful when expressing that the object in question must be a subclass of both X and Y.

The following example works as long as all classes that inherit both X and Y are known in advance:

class X: pass
class Y: pass
class A(X,Y): pass
class B(X,Y): pass

def some_function(arg: Union[A,B]): 
    pass
    # do stuff with arg that only depends on inherited members from X and Y

But what if another package which depends on the code above defines:

class C(X,Y): pass

C also will work in some_function by design. I am looking for a better type hint to use instead of Union[X,Y] that includes any possible class that sub-classes both X and Y.

I understand a workaround could be to define:

class XY(X,Y): pass

And then use it as a base class and type hint:

class A(XY): pass
class B(XY): pass
class C(XY): pass
def some_function(arg: XY): pass

But I am not sure if it would be worthwhile to define a new class only for type hint, which doesn't effect runtime anyway.

How do we create a type hint for any class that is a subclass of both X and Y?

1 Answers

Python type hints does not support explicit intersection annotation. But you have at least two workarounds:

You could introduce a mix class, e.g:

class A:
    def foo_a(self):
        pass

class B:
    def foo_b(self):
        pass
    

class Mix(A, B):
    pass


def foo(p: Mix) -> None:
    p.foo_a()
    p.foo_b()

Or use structural subtyping, Protocol, e.g.:

from typing import Protocol

class Supports_ab(Protocol):
    def a(self):
        pass
    
    def b(self):
        pass

class A:
    def a(self):
        pass

class B:
    def b(self):
        pass

class Derived(A, B):
    pass

    
class SomeClassAB:  # no need superclass
    def a(self):
        pass
    
    def b(self):
        pass
    
def foo(p: Supports_ab) -> None:
    p.a()
    p.b()

foo(SomeClassAB())
foo(Derived())
Related