我一直在努力编写"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年晚些时候实施,这样你就可以在那时消除过载。