Skip to content

Typing for callable inputs in itertools.starmap #14892

@aakhavanQC

Description

@aakhavanQC

The type annotations for itertools.starmap allow any iterables for any function arguments:

@disjoint_base
class starmap(Generic[_T_co]):
def __new__(cls, function: Callable[..., _T], iterable: Iterable[Iterable[Any]], /) -> starmap[_T]: ...
def __iter__(self) -> Self: ...
def __next__(self) -> _T_co: ...

This means I can do something like:

def myfunc(x: int, y: int):
    return x + y

itertools.starmap(myfunc, [["foo, "bar"]])

and type checkers won't complain.

map gets around this to some extent with overloads for callables with up to 5 arguments (but if you need 6 or more, you're out of luck!)

typeshed/stdlib/builtins.pyi

Lines 1629 to 1670 in 11c7821

@overload
def __new__(cls, func: Callable[[_T1], _S], iterable: Iterable[_T1], /) -> Self: ...
@overload
def __new__(cls, func: Callable[[_T1, _T2], _S], iterable: Iterable[_T1], iter2: Iterable[_T2], /) -> Self: ...
@overload
def __new__(
cls, func: Callable[[_T1, _T2, _T3], _S], iterable: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /
) -> Self: ...
@overload
def __new__(
cls,
func: Callable[[_T1, _T2, _T3, _T4], _S],
iterable: Iterable[_T1],
iter2: Iterable[_T2],
iter3: Iterable[_T3],
iter4: Iterable[_T4],
/,
) -> Self: ...
@overload
def __new__(
cls,
func: Callable[[_T1, _T2, _T3, _T4, _T5], _S],
iterable: Iterable[_T1],
iter2: Iterable[_T2],
iter3: Iterable[_T3],
iter4: Iterable[_T4],
iter5: Iterable[_T5],
/,
) -> Self: ...
@overload
def __new__(
cls,
func: Callable[..., _S],
iterable: Iterable[Any],
iter2: Iterable[Any],
iter3: Iterable[Any],
iter4: Iterable[Any],
iter5: Iterable[Any],
iter6: Iterable[Any],
/,
*iterables: Iterable[Any],
) -> Self: ...

I couldn't figure out a way to ge that to work with starmap, though, because as far as I can tell the python type system doesn't have any way of specifying an iterable that returns a particular sequence of types. The best you can do is something like def __new__(cls, function: Callable[[_T1, _T2], T], iterable: Iterable[Iterable[_T1 | _T2]], /), which is still better in my opinion (it prevents invalid argument types), but it doesn't stop you from swapping function arguments.

Is there a way to do this? Or does it just need a new language feature? It would be great if we could do something like

P = ParamSpec("P")
def __new__(cls, function: Callable[P, T], iterable: Iterable[P.args], /)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions