Python 3 键入 varadic "apply" 样式定义



我一直在努力编写"varadic"参数列表类型定义。

例如,将类型赋予:

def foo(fn, *args):
return fn(*args)

我能做的最好的事情就是使用这里的一个建议:

from typing import overload, Callable, TypeVar
A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')
R = TypeVar('R')
@overload
def foo(fn: Callable[[A], R], a: A) -> R: ...
@overload
def foo(fn: Callable[[A, B], R], a: A, b: B) -> R: ...
@overload
def foo(fn: Callable[[A, B, C], R], a: A, b: B, c: C) -> R: ...
def foo(fn, *args):
return fn(*args)

它主要做正确的事情…例如,给定:

def bar(i: int, j: int) -> None:
print(i)

以下操作成功:

foo(bar, 10, 12)

当这些失败时:

foo(bar, 10)
foo(bar, 10, 'a')
foo(bar, 10, 12) + 1

但如果我用mypy --strict检查,我会得到:

test.py:15: error: Function is missing a type annotation

(也就是说最终的foo定义本身没有任何类型)

我可以将foo重新定义为:

def foo(fn: Callable[..., R], *args: Any) -> R:
return fn(*args)

但当我运行mypy --strict时,我得到:

test.py:15: error: Overloaded function implementation does not accept all possible arguments of signature 1
test.py:15: error: Overloaded function implementation does not accept all possible arguments of signature 2
test.py:15: error: Overloaded function implementation does not accept all possible arguments of signature 3

我真的不明白。

如果有人能为这类函数提供一种更好的类型分配方法,我们将不胜感激!如果我也可以这样做,而不列出很多overload,那就太好了,真正的定义也有一些"仅关键字"的参数,不必每次都重复

您得到"重载函数实现不接受所有可能的参数…"错误的原因是您的重载实现没有正确处理如下所示的调用:foo(my_callable, a=3, b=4)

毕竟,根据您的重载签名,理论上用户可以显式地使用a、b、c等的命名参数——因此,您的实现需要支持这些类型的调用。

有两种不同的方法可以解决这个问题。

第一种方法是使用**kwargs: Any,并将重载实现修改为如下所示:

def foo(fn: Callable[..., R], *args: Any, **kwargs: Any) -> Any:
return fn(*args, **kwargs)

现在,您的实现将正确地处理这些类型的调用。

第二种方法是在每个参数前面加两个下划线,如下所示:

@overload
def foo(fn: Callable[[A], R], __a: A) -> R: ...
@overload
def foo(fn: Callable[[A, B], R], __a: A, __b: B) -> R: ...
@overload
def foo(fn: Callable[[A, B, C], R], __a: A, __b: B, __c: C) -> R: ...
def foo(fn: Callable[..., R], *args: Any) -> Any:
return fn(*args)

当mypy看到一个以两个下划线开头的参数时,它就明白了这个参数只能是位置性的。因此,mypy将拒绝类似foo(my_fn, __a=3, __b=4)的调用。

不过,这只是一件打字的事情。在参数前面加两个下划线在运行时没有特殊意义。


关于不必重复这么多过载的更广泛问题:不幸的是,处理一堆过载是我们目前能做的最好的事情。您使用的技术与typeshed用于键入map(...)filter(...)等函数的技术相同。

为了做得更好,我们需要一个名为可变泛型的功能,但它们是一个复杂的功能,不幸的是mypy还不支持它们。不过,该计划有望在2019年晚些时候实施,这样你就可以在那时消除过载。

最新更新