'Mypy type confusion with Union of Callable

I'm relatively new to Python type hinting and mypy and was wondering why the following code fails mypy verification.

from typing import Callable, Union

class A:
    a: int = 1

class B:
    b: int = 1

def foo(cb: Union[Callable[[A], None], Callable[[B], None]]):
    a = A()
    cb(a)

def bar(a: A) -> None:
    print(a)

foo(bar)

This fails with the following message

test.py:11: error: Argument 1 has incompatible type "A"; expected "B"
Found 1 error in 1 file (checked 1 source file)

From reading this, I would assume that the argument cb can either take a Callable[[A], None] or a Callable[[B], None] but it seems that's not the case? Can someone explain what's going on here? Is this a mypy thing and an implementation quirk, are my assumptions wrong or is this somehow expected?

Thanks!



Solution 1:[1]

foo only knows what you told it about cb: either it is a function that takes an A value as an argument, or it is a function that takes a B values as an argument. It is not a function that can take either an A or a B.

Typed as it is, you can't really call cb at all, because you don't know what type it accepts. (You know it's one of two, but not which one.)


Update: to be precise, as pointed out by @joel, you can't call it with a value that is only an instance of A or an instance of B. You could call it with a value that is an instance of both. For example, the following type checks

class C(A, B):
    pass

def foo(cb: Union[Callable[[A], None], Callable[[B], None]]):
    c = C()
    cb(c)

because c satisfies either condition.

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