Chameleon
Chameleon

Reputation: 10138

Python type hint for Callable with variable number of str/same type arguments?

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

Answers (3)

N. Jonas Figge
N. Jonas Figge

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

chepner
chepner

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

Agost Biro
Agost Biro

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

Related Questions