You have a couple of options here, depending on exactly what you're going for.
I'm going to assume for example that foo is defined
def foo(qux: int, thud: float, bar: str) -> str:
# Does whatever
return "Hi"
If we use reveal_type
we find that partial(foo, bar="blah")
is identified as functools.partial[builtins.str*]
. That roughly translates to a function-ish thing which takes anything and returns a string. So, you could annotate it with exactly that, and you'd at least get the return type in your annotation.
def my_func() -> partial[str]:
...
a: str = my_func()(2, 2.5) # Works fine
b: int = my_func()(2, 2.5) # correctly fails, we know we don't get an int
c: str = my_func()("Hello", [12,13]) # Incorrectly passes. We don't know to reject those inputs.
We can be more specific, which takes a bit of care when writing the function, and allows MyPy to better help us later. In general, there are two main options for annotating functions and function-like things. There's Callable and there's Protocol.
Callable is generally more consise and works when you're dealing with positional arguments. Protocol is a bit more verbose, and works with keyword arguments too.
So, you can annotate your function as
def my_func() -> Callable[[int, float], str]:
That is, it returns a function which takes an int (for qux) and a float (for thud) and returns a string. Now, note that MyPy doesn't know what the input type is going to be, so it can't verify that bit. partial[str]
would be just as compatible with Callable[[spam, ham, eggs], str]
. It does, however, pass without errors, and it will then helpfully warn you if you try to pass in the wrong parameters to your Callable. That is,
my_func()(7, 2.6) # This will pass
my_func()("Hello", [12,13]) # This will now correctly fail.
Now, let's suppose instead that foo
were defined as follows.
def foo(qux: int, bar: str, thud: float) -> str:
# Does whatever
return "Hi"
Once we've got a partial
passing bar
as a keyword argument, there is no way to get thud
in as a positional argument. That means there's no way to use Callable to annotate this one. Instead, we have to use a Protocol.
The syntax is a bit weird. It works as follows.
class PartFoo(Protocol):
def __call__(fakeSelf, qux: int, *, thud: float) -> str:
...
Teasing apart that __call__
line, we first have the fakeSelf
entry. That's just notation: if call is a method, the first parameter gets swallowed.
Next, we have qux
, annotated as an int
as before. We then have the *
marker to indicate that everything following is keyword only, because we can no longer reach thud
in the real method positionally. Then we have thud
with its annotation, and finally we have the -> str
to give the return type.
Now if you define def my_func() -> PartFoo:
you get the behaviour we want
my_func()(7, thud=1.5) # Works fine, qux is passed positionally, thud is a kwarg
my_func()(qux=7, thud=1.5) # Works fine, both qux and thud are kwargs
my_func()(7) # Correctly fails because thud is missing
my_func()(7, 1.5) # Correctly fails because thud can't be a positional arg.
The final situation that you could run into is where your original method has optional parameters. So, let's say
def foo(qux: int, bar: str, thud: float = 0.5) -> str:
# Does whatever
return "Hi"
Once again, we can't handle this precisely with Callable
but Protocol
is just fine. We simply ensure that the PartFoo
protocol also specifies a default value for thud
. Here I am using an ellipsis literal, as a gentle reminder that the actual value of that default may differ between implementations and the Protocol.
class PartFoo(Protocol):
def __call__(fakeSelf, qux: int, *, thud: float=...) -> str:
...
Now our behavior is
my_func()(7, thud=1.5) # Works fine, qux is passed positionally, thud is a kwarg
my_func()(qux=7, thud=1.5) # Works fine, both qux and thud are kwargs
my_func()(7) # Works fine because thud is optional
my_func()(7, 1.5) # Correctly fails because thud can't be a positional arg.
To recap, the partial function returns a fairly vague functionish type, which you can use directly but would lose checking against the inputs. You can annotate it with something more specific, using Callable
in simple cases and Protocol
in more complicated ones.