python 3 键入 varadic "apply" 样式定义
python 3 typing varadic "apply" style definitions
我一直在努力编写 "varadic" argument lists 类型定义。
例如,将类型赋予:
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
的情况下做到这一点,那会很好,真正的定义也有一些 "keyword only" 参数,这很好,不必每次都重复
您收到 "Overloaded function implementation does not accept all possible arguments..." 错误的原因是您的重载实现没有正确处理如下所示的调用: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 年晚些时候实施它们,这样您就可以在那时消除过载。
我一直在努力编写 "varadic" argument lists 类型定义。
例如,将类型赋予:
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
的情况下做到这一点,那会很好,真正的定义也有一些 "keyword only" 参数,这很好,不必每次都重复
您收到 "Overloaded function implementation does not accept all possible arguments..." 错误的原因是您的重载实现没有正确处理如下所示的调用: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 年晚些时候实施它们,这样您就可以在那时消除过载。