*args 和 **kwargs 的类型注解
Type annotations for *args and **kwargs
我正在尝试 Python 的带有抽象基础 类 的类型注释来编写一些接口。有没有办法注释 *args
和 **kwargs
的可能类型?
例如,如何表达函数的合理参数是一个 int
或两个 int
? 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
和 **kwargs
的合理类型?
对于可变位置参数(*args
)和可变关键字参数(**kw
),您只需要为一个这样的参数指定预期值。
来自类型提示 PEP 的Arbitrary argument lists and default argument values section:
Arbitrary argument lists can as well be type annotated, so that the definition:
def foo(*args: str, **kwds: int): ...
is acceptable and it means that, e.g., all of the following represent function calls with valid types of arguments:
foo('a', 'b', 'c')
foo(x=1, y=2)
foo('', z=0)
所以你想像这样指定你的方法:
def foo(*args: int):
然而,如果你的函数只能接受一个或两个整数值,你根本不应该使用 *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
正确的方法是使用 @overload
from typing import overload
@overload
def foo(arg1: int, arg2: int) -> int:
...
@overload
def foo(arg: int) -> int:
...
def foo(*args):
try:
i, j = args
return i + j
except ValueError:
assert len(args) == 1
i = args[0]
return i
print(foo(1))
print(foo(1, 2))
请注意,您不要在实际实现中添加 @overload
或类型注释,它们必须放在最后。
您需要 typing
和 mypy 的新版本才能获得对@overload outside of stub files.
的支持
您还可以使用它来改变 returned 结果,明确哪些参数类型对应于哪些 return 类型。例如:
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))
还没有真正支持
虽然您可以使用类型注释可变参数,但我认为它不是很有用,因为它假定所有参数都属于同一类型。
*args
和 **kwargs
的正确类型注释允许单独指定每个可变参数,但 mypy 尚不支持。有人提议在 mypy_extensions
模块上添加一个 Expand
助手,它会像这样工作:
class Options(TypedDict):
timeout: int
alternative: str
on_error: Callable[[int], None]
on_timeout: Callable[[], None]
...
def fun(x: int, *, **options: Expand[Options]) -> None:
...
GitHub issue 于 2018 年 1 月开业,但仍未关闭。请注意,虽然问题是关于 **kwargs
,但 Expand
语法也可能用于 *args
。
如果想要描述 kwargs 中预期的特定命名参数,可以改为传入 TypedDict(定义必需参数和可选参数)。可选参数是 kwargs。
注意:TypedDict 在 python >= 3.8
看这个例子:
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)
在某些情况下,**kwargs 的内容可以是多种类型。
这似乎对我有用:
from typing import Any
def testfunc(**kwargs: Any) -> None:
print(kwargs)
或
from typing import Any, Optional
def testfunc(**kwargs: Optional[Any]) -> None:
print(kwargs)
如果您觉得需要在 **kwargs
中限制类型,我建议创建一个类似结构的对象并在其中添加类型。这可以通过数据类或 pydantic 来完成。
from dataclasses import dataclass
@dataclass
class MyTypedKwargs:
expected_variable: str
other_expected_variable: int
def testfunc(expectedargs: MyTypedKwargs) -> None:
pass
我正在尝试 Python 的带有抽象基础 类 的类型注释来编写一些接口。有没有办法注释 *args
和 **kwargs
的可能类型?
例如,如何表达函数的合理参数是一个 int
或两个 int
? 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
和 **kwargs
的合理类型?
对于可变位置参数(*args
)和可变关键字参数(**kw
),您只需要为一个这样的参数指定预期值。
来自类型提示 PEP 的Arbitrary argument lists and default argument values section:
Arbitrary argument lists can as well be type annotated, so that the definition:
def foo(*args: str, **kwds: int): ...
is acceptable and it means that, e.g., all of the following represent function calls with valid types of arguments:
foo('a', 'b', 'c') foo(x=1, y=2) foo('', z=0)
所以你想像这样指定你的方法:
def foo(*args: int):
然而,如果你的函数只能接受一个或两个整数值,你根本不应该使用 *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
正确的方法是使用 @overload
from typing import overload
@overload
def foo(arg1: int, arg2: int) -> int:
...
@overload
def foo(arg: int) -> int:
...
def foo(*args):
try:
i, j = args
return i + j
except ValueError:
assert len(args) == 1
i = args[0]
return i
print(foo(1))
print(foo(1, 2))
请注意,您不要在实际实现中添加 @overload
或类型注释,它们必须放在最后。
您需要 typing
和 mypy 的新版本才能获得对@overload outside of stub files.
您还可以使用它来改变 returned 结果,明确哪些参数类型对应于哪些 return 类型。例如:
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))
还没有真正支持
虽然您可以使用类型注释可变参数,但我认为它不是很有用,因为它假定所有参数都属于同一类型。
*args
和 **kwargs
的正确类型注释允许单独指定每个可变参数,但 mypy 尚不支持。有人提议在 mypy_extensions
模块上添加一个 Expand
助手,它会像这样工作:
class Options(TypedDict):
timeout: int
alternative: str
on_error: Callable[[int], None]
on_timeout: Callable[[], None]
...
def fun(x: int, *, **options: Expand[Options]) -> None:
...
GitHub issue 于 2018 年 1 月开业,但仍未关闭。请注意,虽然问题是关于 **kwargs
,但 Expand
语法也可能用于 *args
。
如果想要描述 kwargs 中预期的特定命名参数,可以改为传入 TypedDict(定义必需参数和可选参数)。可选参数是 kwargs。 注意:TypedDict 在 python >= 3.8 看这个例子:
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)
在某些情况下,**kwargs 的内容可以是多种类型。
这似乎对我有用:
from typing import Any
def testfunc(**kwargs: Any) -> None:
print(kwargs)
或
from typing import Any, Optional
def testfunc(**kwargs: Optional[Any]) -> None:
print(kwargs)
如果您觉得需要在 **kwargs
中限制类型,我建议创建一个类似结构的对象并在其中添加类型。这可以通过数据类或 pydantic 来完成。
from dataclasses import dataclass
@dataclass
class MyTypedKwargs:
expected_variable: str
other_expected_variable: int
def testfunc(expectedargs: MyTypedKwargs) -> None:
pass