Python *args和**kwargs的类型批注

Python *args和**kwargs的类型批注,python,type-hinting,typechecking,typing,Python,Type Hinting,Typechecking,Typing,我正在试用Python的类型注释和抽象基类来编写一些接口。是否有办法注释*args和**kwargs的可能类型 例如,如何表示函数的合理参数是一个int或两个intstype(args)给出了Tuple,所以我猜应该将该类型注释为Union[Tuple[int,int],Tuple[int]],但这不起作用 from typing import Union, Tuple def foo(*args: Union[Tuple[int, int], Tuple[int]]): try:

我正在试用Python的类型注释和抽象基类来编写一些接口。是否有办法注释
*args
**kwargs
的可能类型

例如,如何表示函数的合理参数是一个
int
或两个
int
s
type(args)
给出了
Tuple
,所以我猜应该将该类型注释为
Union[Tuple[int,int],Tuple[int]]
,但这不起作用

from typing import Union, Tuple

def foo(*args: Union[Tuple[int, int], Tuple[int]]):
    try:
        i, j = args
        return i + j
    except ValueError:
        assert len(args) == 1
        i = args[0]
        return i

# ok
print(foo((1,)))
print(foo((1, 2)))
# mypy does not like this
print(foo(1))
print(foo(1, 2))
来自mypy的错误消息:

t.py: note: In function "foo":
t.py:6: error: Unsupported operand types for + ("tuple" and "Union[Tuple[int, int], Tuple[int]]")
t.py: note: At top level:
t.py:12: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:14: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:15: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:15: error: Argument 2 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
mypy不喜欢函数调用这样做是有道理的,因为它希望在调用本身中有一个
tuple
。解包后添加的内容也会导致一个我不理解的输入错误


对于变量位置参数(
*args
)和变量关键字参数(
**kw
),您只需要为一个这样的参数指定期望值,如何为
*args
**kw
注释合理类型

从PEP的类型提示中:

也可以对任意参数列表进行类型注释,以便定义:

def foo(*args: str, **kwds: int): ...
是可接受的,这意味着,例如,以下所有内容都表示具有有效参数类型的函数调用:

foo('a', 'b', 'c')
foo(x=1, y=2)
foo('', z=0)
因此,您希望像这样指定您的方法:

def foo(*args: int):
class Options(TypedDict):
    timeout: int
    alternative: str
    on_error: Callable[[int], None]
    on_timeout: Callable[[], None]
    ...

def fun(x: int, *, **options: Expand[Options]) -> None:
    ...
但是,如果函数只能接受一个或两个整数值,则根本不应使用
*args
,请使用一个显式位置参数和第二个关键字参数:

def foo(first: int, second: Optional[int] = None):

现在,函数实际上被限制为一个或两个参数,如果指定,这两个参数都必须是整数
*args
始终表示0或更多,并且不能被类型提示限制为更具体的范围。

作为对前面答案的简短补充,如果您试图在Python 2文件上使用mypy,并且需要使用注释来添加类型而不是注释,您需要在
args
kwargs
的类型前面分别加上
*
**

def foo(param, *args, **kwargs):
    # type: (bool, *str, **int) -> None
    pass
mypy将其视为与以下相同的Python 3.5版本的
foo

def foo(param: bool, *args: str, **kwargs: int) -> None:
    pass

正确的方法是使用

请注意,您没有向实际实现中添加
@重载
或类型注释,这些注释必须在最后

您需要一个新版本的
键入
和mypy来获得对@重载的支持

您还可以使用它来改变返回的结果,从而明确哪些参数类型对应于哪个返回类型。e、 g:

from typing import Tuple, overload

@overload
def foo(arg1: int, arg2: int) -> Tuple[int, int]:
    ...

@overload
def foo(arg: int) -> int:
    ...

def foo(*args):
    try:
        i, j = args
        return j, i
    except ValueError:
        assert len(args) == 1
        i = args[0]
        return i

print(foo(1))
print(foo(1, 2))
还没有真正得到支持 虽然可以用一个类型来注释变量参数,但我觉得它不是很有用,因为它假设所有参数都是同一类型的

mypy还不支持允许单独指定每个变量参数的
*args
**kwargs
的正确类型注释。有人建议在
mypy_extensions
模块上添加
Expand
helper,其工作原理如下:

def foo(*args: int):
class Options(TypedDict):
    timeout: int
    alternative: str
    on_error: Callable[[int], None]
    on_timeout: Callable[[], None]
    ...

def fun(x: int, *, **options: Expand[Options]) -> None:
    ...

酒店于2018年1月开业,但至今仍未关闭。请注意,虽然问题是关于
**kwargs
,但
扩展
语法也可能用于
*args

如果要描述kwargs中预期的特定命名参数,可以传递定义必需和可选参数的TypedDict。可选参数是kwargs的名称:

import typing

class RequiredProps(typing.TypedDict):
    # all of these must be present
    a: int
    b: str

class OptionalProps(typing.TypedDict, total=False):
    # these can be included or they can be omitted
    c: int
    d: int

class ReqAndOptional(RequiredProps, OptionalProps):
    pass

def hi(req_and_optional: ReqAndOptional):
    print(req_and_optional)

在某些情况下,**KWARG的内容可以是多种类型

这似乎对我有用:

从键入import Any
def testfunc(**kwargs:Any)->无:
印刷品(kwargs)

从键入import Any开始,可选
def testfunc(**kwargs:可选[任何]->无:
印刷品(kwargs)
如果您觉得有必要约束
**kwargs
中的类型,我建议创建一个类似结构的对象,并在其中添加类型。这可以通过数据类或pydantic实现

from dataclasses import dataclass

@dataclass
class MyTypedKwargs:
   expected_variable: str
   other_expected_variable: int


def testfunc(expectedargs: MyTypedKwargs) -> None:
    pass

我喜欢这个答案,因为它解决了更一般的情况。回顾过去,我不应该使用
(type1)
vs
(type1,type1)
函数调用作为我的示例。也许
(type1)
vs
(type2,type1)
会是一个更好的例子,并说明我为什么喜欢这个答案。这也允许不同的返回类型。但是,在只有一个返回类型且
*args
*kwargs
都是同一类型的特殊情况下,Martjin答案中的技巧更有意义,因此两个答案都很有用。在参数数量最多的情况下使用
*args
但是仍然是错误的。@MartijnPieters为什么这里的
*args
一定是错误的?如果预期的调用是
(type1)
(type2,type1)
,则参数的数量是可变的,并且尾部参数没有适当的默认值。为什么有一个max很重要?
*args确实适用于零个或多个不带上限的同质参数,或者用于“将这些参数传递给未触及的”捕获。您有一个必选参数和一个可选参数。这是完全不同的,通常通过给第二个参数一个sentinel默认值来处理,以检测被忽略的参数。虽然这个答案展示了一种对
*args
类型进行单独注释的有趣方式,但对这个问题更好的回答是,这根本不是应该做的事情。只是好奇,为什么要添加
可选的
?Python有什么变化吗?还是你改变了主意?由于
None
default,是否仍然没有严格的必要?@praxelitic是的,在实践中,当您使用
None
作为默认值时,自动的、隐含的
可选的
注释会使某些用例变得更加困难,并且现在正在从PEP中删除。对于那些感兴趣的人。的确如此