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

时间:2019-06-12 14:35:06

标签: python typing mypy

我正在编写一个带有以下签名的名为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的输入参数进行匹配。

1 个答案:

答案 0 :(得分:1)

不幸的是,没有一种表达这种类型签名的干净方法。为此,我们需要一个名为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个args的可调用对象执行精确的类型检查。

但是实际上,这通常已经足够了。实用上,大多数可调用对象的时间都不太长,如果需要,我们总是可以处理更多的重载来处理更多的特殊情况。

实际上,这种技术实际上是用来为ziphttps://github.com/python/typeshed/blob/master/stdlib/2and3/builtins.pyi#L1403

定义类型的。