Search code examples
pythoninheritancetype-hinting

Why is Python's type hint with Callable with base class as argument not compatible with parameter with child class


I have the following two classes:

class MessageBase:
    pass

class MessageChild(MessageBase):
    pass

And the following function:

def action(message: MessageChild):
    pass

Why does the following type hint for the given get_action function produces an error in Pylance? Normally type hints using base classes are compatible with child classes.

def get_action() -> Callable[[MessageBase], None]:
    return action
Expression of type "(message: MessageChild) -> None" cannot be assigned to return type "(MessageBase) -> None"
  Type "(message: MessageChild) -> None" cannot be assigned to type "(MessageBase) -> None"
    Parameter 1: type "MessageBase" cannot be assigned to type "MessageChild"
      "MessageBase" is incompatible with "MessageChild"

Solution

  • If you flesh out your example a bit, you see why you get this warning:

    class MessageChild(MessageBase):
        def method_only_child_has(self):
            pass
    
    
    def action(message: MessageChild):
        message.method_only_child_has()
    

    We've added something to MessageChild which does not exist in MessageBase. action expects a MessageChild and can thus rely on that special something to be there.

    Now:

    the_action = get_action()
    the_action(MessageBase())
    

    According to the type annotation on get_action, this is supposed to work. the_action supposedly expects an argument of type MessageBase, which we're providing here as requested. When you actually execute this code though, you'll get an AttributeError, since method_only_child_has does not in fact exist on MessageBase.

    That's the type error it's warning you about. It's not about an insufficient type annotation, it's correctly warning you about an actual potential error case.