Python 通用 *args 的类型提示(特别是 zip 或 zipWith)
Python type hints for generic *args (specifically zip or zipWith)
我正在编写一个名为 zip_with
的函数,其签名如下:
_A = TypeVar("_A")
_B = TypeVar("_B")
_C = TypeVar("_C")
def zip_with(zipper: Callable[[_A, _B], _C], a_vals: Iterable[_A], b_vals: Iterable[_B]) -> Generator[_C, None, None]: ...
它类似于 zip
,但允许您使用任意函数进行聚合。这适用于只允许 2 个参数的 zip_with
的实现。
是否支持为可变数量的参数添加类型提示?具体来说,我想要一个任意的泛型类型列表,并且我希望类型检查器能够将参数的类型与 zipper
的参数相匹配。以下是我如何在没有特定类型的情况下做到这一点:
def zip_with(zipper: Callable[..., _C], *vals: Iterable) -> Generator[_C, None, None]: ...
换句话说,我希望类型检查器能够将 *vals
的类型与 zipper
的输入参数相匹配。
不幸的是,没有一种简洁的方式来表达这种类型签名。为此,我们需要一个名为 variadic generics 的功能。虽然 普遍对将此概念添加到 PEP 484 感兴趣,但它可能不会在 short-term 中发生。
特别是对于 mypy 核心团队,我粗略估计这项功能的工作可能会在今年晚些时候暂定开始,但可能最早要到 2020 年初至中期才能普遍使用。 (这是基于与他们团队不同成员的一些 in-person 对话。)
当前的解决方法是像这样滥用重载:
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 个参数的可调用对象执行精确 type-checking。
但实际上,这通常就足够了。从实用的角度来说,大多数可调用对象都不会太长,如果需要的话,我们总是可以添加更多的重载来处理更多的特殊情况。
事实上,这种技术实际上是用于定义 zip
类型的技术:https://github.com/python/typeshed/blob/master/stdlib/2and3/builtins.pyi#L1403
我正在编写一个名为 zip_with
的函数,其签名如下:
_A = TypeVar("_A")
_B = TypeVar("_B")
_C = TypeVar("_C")
def zip_with(zipper: Callable[[_A, _B], _C], a_vals: Iterable[_A], b_vals: Iterable[_B]) -> Generator[_C, None, None]: ...
它类似于 zip
,但允许您使用任意函数进行聚合。这适用于只允许 2 个参数的 zip_with
的实现。
是否支持为可变数量的参数添加类型提示?具体来说,我想要一个任意的泛型类型列表,并且我希望类型检查器能够将参数的类型与 zipper
的参数相匹配。以下是我如何在没有特定类型的情况下做到这一点:
def zip_with(zipper: Callable[..., _C], *vals: Iterable) -> Generator[_C, None, None]: ...
换句话说,我希望类型检查器能够将 *vals
的类型与 zipper
的输入参数相匹配。
不幸的是,没有一种简洁的方式来表达这种类型签名。为此,我们需要一个名为 variadic generics 的功能。虽然 普遍对将此概念添加到 PEP 484 感兴趣,但它可能不会在 short-term 中发生。
特别是对于 mypy 核心团队,我粗略估计这项功能的工作可能会在今年晚些时候暂定开始,但可能最早要到 2020 年初至中期才能普遍使用。 (这是基于与他们团队不同成员的一些 in-person 对话。)
当前的解决方法是像这样滥用重载:
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 个参数的可调用对象执行精确 type-checking。
但实际上,这通常就足够了。从实用的角度来说,大多数可调用对象都不会太长,如果需要的话,我们总是可以添加更多的重载来处理更多的特殊情况。
事实上,这种技术实际上是用于定义 zip
类型的技术:https://github.com/python/typeshed/blob/master/stdlib/2and3/builtins.pyi#L1403