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