DonLarry
DonLarry

Reputation: 955

How to type hint a Callable of a function with default arguments?

I'm trying to Type Hint the function bar, but I got the Too few arguments error when I run mypy.

from typing import Callable, Optional

def foo(arg: int = 123) -> float:
    return arg+0.1

def bar(foo: Callable[[int], float], arg: Optional[int] = None) -> float:
    if arg:
        return foo(arg)
    return foo()

print(bar(foo))
print(bar(foo, 90))

I have also tried:

So, how should I do the Type Hinting of the bar function?

Upvotes: 47

Views: 25081

Answers (2)

cottontail
cottontail

Reputation: 23381

Since an integer can be optionally passed to foo(), we can use the @overload decorator here. Since there are two possibilities, we need two overloads. The first overload type-checks bar(foo, arg) for the case where some integer is passed to foo(). The second overload is type-checking bar(foo, arg) for the case where nothing is passed to foo(). After the overloads, we can define bar() without type hints.

from typing import Callable, Optional, overload

def foo(arg: int = 123) -> float:
    return arg+0.1

@overload
def bar(foo: Callable[[int], float], arg: Optional[int] = None) -> float: ...

@overload
def bar(foo: Callable[[], float], arg: Optional[int] = None) -> float: ...

def bar(foo, arg=None):
    if arg:
        return foo(arg)
    return foo()


print(bar(foo))
print(bar(foo, 90))

Then, for example on VS Code, you can see the hint for bar() function as follows. You can see that foo is a callable to which you can pass an integer or not.

VS Code hint #1


Tested on mypy==1.8.0.

Upvotes: 1

Mario Ishac
Mario Ishac

Reputation: 5907

Define this:

class Foo(Protocol):
    def __call__(self, x: int = ..., /) -> float:
        ...

then type hint foo as Foo instead of Callable[[int], float]. Callback protocols allow you to:

define flexible callback types that are hard (or even impossible) to express using the Callable[...] syntax

and optional arguments are one of those impossible things to express with a normal Callable. The / at the end of __call__'s signature makes x a positional-only parameter, which allows any passed function to bar to have a parameter name that is not x (your specific example of foo calls it arg instead). If you removed /, then not only would the types have to line up as expected, but the names would have to line up too because you would be implying that Foo could be called with a keyword argument. Because bar doesn't call foo with keyword arguments, opting into that behavior by omitting the / imposes inflexibility on the user of bar (and would make your current example still fail because "arg" != "x").

Upvotes: 58

Related Questions