当类型提示 python 函数时,为什么 `*args` 和 `**kwargs` 的处理方式不同?
我正在学习现代 python 中的类型提示,特别是如何表达函数的类型及其参数。
如果我有一个函数 f
并且我对它一无所知,我可以将其类型写为 Callable
(或等效地,Callable[..., Any]< /代码>);如果我知道它的返回类型,我可以指定 Callable[..., ReturnType],最后如果我知道它的所有内容,我可以编写 Callable[[Arg1Type, Arg2Type, \ldots ],返回类型]
。
如果我只知道一些参数类型,但仍想执行该契约,我该怎么办? stackoverflow 上的 This 答案表明,一种有用的方法是创建一个带有合适示例的 Protocol
通话应该是什么样子。
这样做可以让我指定一个类型,例如“一个函数,它采用整数作为第一个参数,然后是任意数量的位置参数,并返回一个整数”。事实上,下面的类型检查:
from typing_extensions import Protocol
class Distribution(Protocol):
def __call__(self, x: int, *args) -> int:
...
def f1(x: int, s: str) -> int:
...
def f2(x: int, a: float, b: float) -> int:
...
distribution: Distribution = f1
distribution: Distribution = f2
太棒了!
但是,如果我想表达一个函数的类型,该函数将整数作为第一个参数,然后是任意数量的关键字参数,并返回一个整数,该怎么办?显而易见的方法是将协议更改为
class Distribution(Protocol):
def __call__(self, x: int, **kwargs) -> int:
...
不幸的是,这不起作用;类型检查器抱怨 **kwargs
在 f1
或 f2
中没有相应的参数。现在,我可以说服自己,无论结果(错误与否)都是有效的:
显然代码无法工作!该协议指定该函数应采用任意数量的关键字参数,而不是某些特定的参数。事实上,将
f1
的签名更改为f1(x: int, s: str, **_)
可以对所有内容进行类型检查。在此视图中,Callable[[a, arg1], a]
不是Callable[[a, **kwargs], a]
的有效子类型。显然代码应该可以工作!我指定我想要一个存在的特定参数,然后任何其他参数都在
*args
元组的其余部分中处理。f1
和f2
都符合此规范,因此没有问题。在此视图中,Callable[[a, arg1], a]
是Callable[[a, *args], a]
的有效子类型。
我无法理解的是,为什么一个示例有效而另一个示例无效。
有谁知道吗?
编辑:这似乎是pyright中的一个错误,因为以下内容也通过了,没有任何投诉
from typing_extensions import Protocol
class Distribution(Protocol):
def __call__(self, x: int, *args) -> int:
...
def no_parameters(x: int) -> int:
...
distribution: Distribution = no_parameters
查看他们的github,它可能是 五个小时前已修复,但我还没有测试过。
I'm learning my way around type hints in modern python, specifically how to express the type of functions and their parameters.
If I have a function f
and I know nothing about it, I can write its type as Callable
(or equivalently, Callable[..., Any]
); if I know its return type, I can specify Callable[..., ReturnType]
, and finally if I know everything about it, I can write Callable[[Arg1Type, Arg2Type, \ldots], ReturnType]
.
What do I do if I only know some of the argument types, but still want to enforce that contract? This answer on stackoverflow suggests that a useful approach is to create a Protocol
with a suitable example of what the call should look like.
Doing that lets me specify a type like "A function that takes an integer as the first parameter, and then an arbitrary number of positional arguments, and returns an integer". And indeed, the following type checks:
from typing_extensions import Protocol
class Distribution(Protocol):
def __call__(self, x: int, *args) -> int:
...
def f1(x: int, s: str) -> int:
...
def f2(x: int, a: float, b: float) -> int:
...
distribution: Distribution = f1
distribution: Distribution = f2
That's great!
But what if I want to express the type of a function that takes an integer as its first argument and then an arbitrary number of keyword arguments, and returns an integer? The obvious approach would be to change the protocol to
class Distribution(Protocol):
def __call__(self, x: int, **kwargs) -> int:
...
Unfortunately this doesn't work; the type checker complains that **kwargs
has no corresponding parameter in either f1
or f2
. Now, I can just about convince myself that either result (error or not) is valid:
Obivously the code can't work! The protocol specifies that the function should take an arbitrary number of keyword arguments, not some specific ones. Indeed, changing the signature of
f1
tof1(x: int, s: str, **_)
makes everything type check. In this view,Callable[[a, arg1], a]
is not a valid subtype ofCallable[[a, **kwargs], a]
.Obviously the code should work! I specify that I want a specific argument, which is present, and then any further arguments are handled in the rest of the
*args
tuple. Bothf1
andf2
match this spec, so there are no issues. In this view,Callable[[a, arg1], a]
is a valid subtype ofCallable[[a, *args], a]
.
What I cannot understand, is why one example works and the other doesn't.
Does anyone know?
Edit: This appears to be a bug in pyright since the following also passes with no complaint
from typing_extensions import Protocol
class Distribution(Protocol):
def __call__(self, x: int, *args) -> int:
...
def no_parameters(x: int) -> int:
...
distribution: Distribution = no_parameters
Looking at their github it might have been fixed five hours ago, but I haven't tested it yet.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论