通用 *args 的 Python 类型提示(特别是 zip 或 zipWith)

问题描述 投票:0回答:1

我正在编写一个名为

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
的输入参数进行匹配。

python python-typing mypy
1个回答
6
投票

不幸的是,没有一种干净的方式来表达这种类型签名。为此,我们需要一个名为 variadic generics 的功能。虽然人们普遍有兴趣将这一概念添加到 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

© www.soinside.com 2019 - 2024. All rights reserved.