Search code examples
pythonpython-3.xtype-hintingmypy

MyPy Incompatible default for argument - "Type[str]" vs "Callable[[str], obj]"


I have a function that accepts a Callable as one of its parameters, and returns whatever that Callable returns. So I want to enforce that the return type of my function do_stuff() is the same as the return type of its parameter some_callable.

MyPy has no complaints about this:

CallableResult = TypeVar("CallableResult")


def do_stuff(
    some_text: Text,
    some_callable: Callable[[Text], CallableResult],
) -> CallableResult:
    """Do stuff"""
    return some_callable(some_text)

I'd like to also supply a default argument for the Callable parameter some_callable, such as 'str':

def do_stuff_with_default(
    some_text: Text,
    some_callable: Callable[[Text], CallableResult] = str,
) -> CallableResult:
    """Do stuff"""
    return some_callable(some_text)

But then MyPy raises an error on the parameter declaration for some_callable:

error: Incompatible default for argument "some_callable" (default has type "Type[str]", argument has type "Callable[[str], CallableResult]")

That confuses me, because while str IS a Type, isn't it also a Callable? If so, why does MyPy reject str as the parameter default value?

I can silence the error by adding constraints to my TypeVar() declaration:

CallableResult = TypeVar("CallableResult", Any, Text)

But I'm not sure is this actually enforces my function's return type in the way that I want.


Solution

  • As Guido Van Rossum pointed out in this mypy issue you can use typing.overload:

    from typing import TYPE_CHECKING, Any, Callable, Text, TypeVar, overload
    from typing_extensions import reveal_type
    
    
    CallableResult = TypeVar("CallableResult")
    
    
    @overload
    def do_stuff_with_default(
        some_text: Text,
        some_callable: Callable[[Text], CallableResult],
    ) -> CallableResult:
        ...
    
    
    @overload
    def do_stuff_with_default(
        some_text: Text,
    ) -> str:
        ...
    
    
    def do_stuff_with_default(
        some_text: Text,
        some_callable: Callable[[Text], CallableResult] | Callable[..., str] = str,
    ) -> CallableResult | str:
        """Do stuff"""
        return some_callable(some_text)
    
    
    x = do_stuff_with_default(1)
    y = do_stuff_with_default(1, int)
    
    if TYPE_CHECKING:
        reveal_type(x)  # revealed type is str
        reveal_type(y)  # revealed type is int