Reputation: 10138
How can I specify one type for all of these callables:
a(str)
b(str, str)
c(str, str, str)
d(str, str, str, str)
I found that I can specify Callable[..., None]
in a general way, but how to specify with details that all arguments will be str
without doing ugly syntax Union[Callable[[str], None], Callable[[str, str], None, __more_like_this__]
?
Is there any other method to do it? Can I do it with typing
?
Upvotes: 26
Views: 21751
Reputation: 152
For everyone looking for a solution for starred / variadic arguments:
From PEP 646:
Using an unpacked unbounded tuple is equivalent to the PEP 484 behavior of *args: int, which accepts zero or more values of type int:
def foo(*args: *Tuple[int, ...]) -> None: ... # equivalent to: def foo(*args: int) -> None: ...
So, if you have a function like this:
def f(*strings: str) -> int:
...
type hinting it should look like this:
from typing import Callable, Tuple
f: Callable[[*Tuple[str, ...]], int]
(I hope I am getting this right since PyCharm still shows wiggly lines, might edit.)
Upvotes: 6
Reputation: 532238
What you want is the union of 4 distinct types.
t1 = Union[
Callable[[str], Any],
Callable[[str, str], Any],
Callable[[str, str, str], Any],
Callable[[str, str, str, str], Any],
]
Constrast that with the type of function that can accept 1 to 4 arguments:
t2 = Callable[[str, Optional[str], Optional[str], Optional[str]], Any]
A function like
def foo(x: str, y: str):
does not belong to the second type above. If I say I need a function of type t2
, you don't know how many arguments I might try to pass. I could give you foo
, but then you might try to pass 3 arguments, or only 1, and get a TypeError
.
If you ask for a function of type t1
, it's OK for me to give you foo
. t1
does not promise that all values in t1
can more or less than exactly 3 arguments; it only includes values that do.
Upvotes: 6
Reputation: 2839
You could use a callback protocol to specify a function type with variadic string arguments:
from typing_extensions import Protocol
class Callback(Protocol):
def __call__(self, *args: str) -> None: ...
And use it like this:
def handler(cb: Callback) -> None:
cb('a', 'b', 'c')
def callback(*args: str) -> None:
pass
handler(callback)
Note that the callback has to take variadic arguments, e.g. this won't work:
def callback(a: str, b: str) -> None:
pass
Protocols were added in Python 3.8 to the standard library typing module, so if you want to use them on Python 3.5-3.7, you will need to install the typing-extensions module from PyPI.
Upvotes: 21