К сожалению, нет чистого способа выразить такую подпись типа. Для этого нам понадобится функция под названием универсальные переменные . Хотя * в целом заинтересован в добавлении этой концепции к PEP 484, в краткосрочной перспективе этого, вероятно, не произойдет.
В частности, для основной команды mypy эта работа над этой функцией, возможно, ориентировочно может начаться позднее в этом году, но, вероятно, будет недоступна для общего использования до начала-середины 2020 года, как минимум. (Это основано на личных беседах с различными членами их команды.)
Текущий обходной путь - использовать следующие перегрузки:
from typing import TypeVar, overload, Callable, Iterable, Any, Generator
_T1 = TypeVar("_T1")
_T2 = TypeVar("_T2")
_T3 = TypeVar("_T3")
_T4 = TypeVar("_T4")
_T5 = TypeVar("_T5")
_TRet = TypeVar("_TRet")
@overload
def zip_with(zipper: Callable[[_T1, _T2], _TRet],
__vals1: Iterable[_T1],
__vals2: Iterable[_T2],
) -> Generator[_TRet, None, None]: ...
@overload
def zip_with(zipper: Callable[[_T1, _T2, _T3], _TRet],
__vals1: Iterable[_T1],
__vals2: Iterable[_T2],
__vals3: Iterable[_T3],
) -> Generator[_TRet, None, None]: ...
@overload
def zip_with(zipper: Callable[[_T1, _T2, _T3, _T4], _TRet],
__vals1: Iterable[_T1],
__vals2: Iterable[_T2],
__vals3: Iterable[_T3],
__vals4: Iterable[_T4],
) -> Generator[_TRet, None, None]: ...
@overload
def zip_with(zipper: Callable[[_T1, _T2, _T3, _T4, _T5], _TRet],
__vals1: Iterable[_T1],
__vals2: Iterable[_T2],
__vals3: Iterable[_T3],
__vals4: Iterable[_T4],
__vals5: Iterable[_T5],
) -> Generator[_TRet, None, None]: ...
# One final fallback overload if we want to handle callables with more than
# 5 args more gracefully. (We can omit this if we want to bias towards
# full precision at the cost of usability.)
@overload
def zip_with(zipper: Callable[..., _TRet],
*__vals: Iterable[Any],
) -> Generator[_TRet, None, None]: ...
def zip_with(zipper: Callable[..., _TRet],
*__vals: Iterable[Any],
) -> Generator[_TRet, None, None]:
pass
Этот подход, очевидно, довольно не элегантный - писать неуклюже, и он выполняет точную проверку типов только для вызываемых элементов, которые принимают до 5 аргументов.
Но на практике это обычно достаточно хорошо. С практической точки зрения, большинство вызываемых объектов не слишком длинные, и мы всегда можем использовать больше перегрузок, чтобы при необходимости обрабатывать больше особых случаев.
И на самом деле, эта техника фактически используется для определения типов для zip
: https://github.com/python/typeshed/blob/master/stdlib/2and3/builtins.pyi#L1403