28

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:

  • Callable[[], float] (got Too many arguments error)
  • Callable[[Optional[int]], float] (got another error)

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

DonLarry
  • 702
  • 2
  • 11
  • 22

1 Answers1

32

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").

Mario Ishac
  • 5,060
  • 3
  • 21
  • 52
  • How do you know that > "optional arguments are one of those impossible things to express with a normal Callable"? – DonLarry Jul 16 '21 at 14:47
  • 4
    @DonLarry Arguments with type `Optional` (`Callable[[Optional[int]], bool`) are possible to express, but you need to pass `None`. Optional arguments (so arguments with defaults) where you don't have to pass anything are not supported because it would require being able to specify `=` for the generic arguments of a `Callable` (something like `Callable[[int = 123], bool]`, which is an unsupported syntax. – Mario Ishac Jul 16 '21 at 20:18
  • 2
    Technically it wouldn't have to be `=`, could be some other syntax, but none have been agreed upon (https://github.com/python/typing/issues/264) outside of `Protocol`s. – Mario Ishac Jul 16 '21 at 20:20
  • bro, it is way different arguments with default and Optional arguments, look at this https://docs.python.org/3/library/typing.html#typing.Optional "Note that this is not the same concept as an optional argument, which is one that has a default." – DonLarry Jul 17 '21 at 01:25
  • 2
    @DonLarry yes, that's why I made the distinction. – Mario Ishac Jul 17 '21 at 03:53