As MisterMiyagi pointed out - The type check warning is correct.
B inherits from A, so something_a(x: A) can accept both A and B, whereas something_b(x: B) can only accept B. (covariance)
If we pass something_b(x: B) to do(f: Callable[[A], None]), we allow something_b(x: B) to accept an A - which it can't. So something_b(x: B) can't substitute something_a(x: A). On the other hand passing something_a(x: A) to a function that expects a Callable[[B], None] would've been okay. (contravariance)
What to do? - use Generics
AType = TypeVar('AType', bound=A) def do_fixed(f: Callable[[AType], None]): pass do_fixed(something_a) # ok do_fixed(something_b) # ok
While we do get a warning for passing other type of callable:
class C: # not derived from A n = 1 def something_c(x: C): print(x.n) do_fixed(something_c) # type check warning
But we still need to make sure we're not passing A to something_b, which can be achieved by:
def do_safer(f: Callable[[AType], None], v: AType): f(v) do_safer(something_a, A()) # ok do_safer(something_a, B()) # ok do_safer(something_b, A()) # type check warning do_safer(something_b, B()) # ok
doonly takesf: (A) -> Nonefunctions, it could merely callf(A())without violating its contract tof.something_aworks with that,something_bdoes not (it requires "at least" aB). You might want to read up on co- and contravariance.f(B())is perfectly valid (not violatingCallable[[A], None]). I do see your point however that if I allowdoto acceptsomething_b, than it could pass it anA, which could "break"something_b's contract. Thanks, Missed that.doactually takes af: (B) -> None.