Search code examples
pythonpython-typingmypy

type safety (mypy) for function parameters when using *args


Type-checking the following code with mypy:

def foo(a: str, b: float, c: int):
    print(a, b, c + 1)

foo('ok', 2.2, 'bad')

reveals the invalid call too foo with:

error: Argument 3 to "foo" has incompatible type "str"; expected "int"

Now let's say we have a wrapper function like the following:

from typing import Callable, Any

def say_hi_and_call(func: Callable[..., Any], *args):
    print('Hi.')
    func(*args)

and do an invalid call using it

say_hi_and_call(foo, 'ok', 2.2, 'bad')

mypy will not report any errors, instead we will only get to know about this error at runtime:

TypeError: must be str, not int

I'd like to catch this error earlier. Is there a possibility to refine the type annotations in a way that mypy is able to report the problem?


Solution

  • OK, the only solution I came up with is making the arity of the function explicit, i.e.

    from typing import Any, Callable, TypeVar
    
    A = TypeVar('A')
    B = TypeVar('B')
    C = TypeVar('C')
    
    def say_hi_and_call_ternary(func: Callable[[A, B, C], Any], a: A, b: B, c: C):
        print('Hi.')
        func(a, b, c)
    
    def foo(a: str, b: float, c: int):
        print(a, b, c + 1)
    
    say_hi_and_call_ternary(foo, 'ok', 2.2, 'bad')
    

    Of course one would need a similar say_hi_and_call_unary and say_hi_and_call_binary etc. too.

    But since I value my application not exploding in PROD over saving some LOC, I'm happy when mypy is able to report the error, which now certainly is the case:

    error: Argument 1 to "say_hi_and_call_ternary" has incompatible type "Callable[[str, float, int], Any]"; expected "Callable[[str, float, str], Any]"