Pra*_*tic 99 python typing typechecking type-hinting
我正在尝试使用抽象基类的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))
Run Code Online (Sandbox Code Playgroud)
来自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]]"
Run Code Online (Sandbox Code Playgroud)
有意义的是mypy对函数调用不喜欢这个,因为它期望tuple在调用本身中有一个.解包后的添加也会出现我不理解的输入错误.
如何用*args和诠释明智的类型**kwargs?
Mar*_*ers 106
对于变量位置参数(*args)和变量关键字参数(**kw),您只需要为一个这样的参数指定期望值.
从任意参数列表和默认参数值部分的的类型提示 PEP:
任意参数列表也可以是类型注释,以便定义:
Run Code Online (Sandbox Code Playgroud)def foo(*args: str, **kwds: int): ...是可接受的,这意味着,例如,以下所有代表使用有效类型的参数的函数调用:
Run Code Online (Sandbox Code Playgroud)foo('a', 'b', 'c') foo(x=1, y=2) foo('', z=0)
所以你想要像这样指定你的方法:
def foo(*args: int):
Run Code Online (Sandbox Code Playgroud)
但是,如果您的函数只能接受一个或两个整数值,则根本不应使用*args,使用一个显式位置参数和第二个关键字参数:
def foo(first: int, second: Optional[int] = None):
Run Code Online (Sandbox Code Playgroud)
现在你的函数实际上只限于一个或两个参数,如果指定,它们都必须是整数.*args 始终表示0或更多,并且不能通过类型提示限制到更具体的范围.
Mic*_*x2a 17
作为短除了前面的答案,如果你想对Python的使用mypy 2个文件,需要使用注释,而不是添加类型的注释,则需要前缀类型args和kwargs与*和**分别为:
def foo(param, *args, **kwargs):
# type: (bool, *str, **int) -> None
pass
Run Code Online (Sandbox Code Playgroud)
mypy将其视为与以下Python 3.5版本相同foo:
def foo(param: bool, *args: str, **kwargs: int) -> None:
pass
Run Code Online (Sandbox Code Playgroud)
Ces*_*ssa 16
虽然您可以使用类型注释可变参数,但我认为它不是很有用,因为它假定所有参数都属于同一类型。
mypy 尚不支持允许分别指定每个可变参数的*argsand的正确类型注释**kwargs。有一个Expand在mypy_extensions模块上添加帮助程序的建议,它的工作方式如下:
class Options(TypedDict):
timeout: int
alternative: str
on_error: Callable[[int], None]
on_timeout: Callable[[], None]
...
def fun(x: int, *, **options: Expand[Options]) -> None:
...
Run Code Online (Sandbox Code Playgroud)
在GitHub的问题被打开了2018一月,但它仍然没有关闭。请注意,虽然问题是关于**kwargs,但Expand语法也可能用于*args。
cha*_*rik 14
正确的方法是使用 @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))
Run Code Online (Sandbox Code Playgroud)
请注意,您不会@overload向实际实现添加或键入注释,这必须是最后的.
你需要一个新版本的typingmy和mypy来获得对存根文件之外的 @overload的支持.
您还可以使用它来改变返回的结果,使显式哪些参数类型与哪种返回类型相对应.例如:
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))
Run Code Online (Sandbox Code Playgroud)
A. *_*dry 10
我正在尝试使用 Python 的类型注释和抽象基类来编写一些接口。
*args有没有一种方法可以注释and 的可能类型**kwargs...如何注释*argsand的合理类型**kwargs
类型提示有两种一般用法类别:
大多数用户都具有两者的组合。
答案取决于您的*args和是否**kwargs具有同质类型(即全部相同类型)或异质类型(即不同类型),以及它们的数量是固定数量还是可变/不确定数量(使用的术语这是固定数量与可变数量)
*args有时**kwargs被用在我松散地称为“ Python 特定设计模式”的内容中(见下文)。了解何时执行此操作很重要,因为它会影响您输入提示的方式。
最佳实践始终是站在巨人的肩膀上:
typeshed .pyi存根,尤其是标准库,以了解开发人员如何在野外输入这些内容。对于那些希望看到如何实现的人,请考虑对以下 PR 进行投票:
*args(a)对数量可变的同质参数进行运算
使用的第一个原因*args是编写一个必须处理可变(不确定)数量的同质参数的函数
示例:求和、接受命令行参数等。
在这些情况下,所有的*args都是同质的(即都是相同的类型)。
示例:在第一种情况下,所有参数都是ints 或floats;在第二种情况下,所有参数都是strs。
也可以使用Unions、TypeAliass、Generics 和Protocols 作为 的类型*args。
我声称(没有证据)对不确定数量的同质参数进行操作是*args引入 Python 语言的第一个原因。
因此,PEP 484支持提供*args同质类型。
笔记:
使用
*args比显式指定参数要少得多(即从逻辑上讲,您的代码库不使用的函数*args比 do多得多)。使用*args同类类型通常是为了避免要求用户在将参数传递给 函数之前将参数放入容器中。建议尽可能明确地键入参数。
- 如果没有其他原因,您通常会在文档字符串中记录每个参数及其类型(不记录是让其他人不想使用您的代码的快速方法,包括您未来的自己。)
另请注意,这
args是一个元组,因为解包运算符 (*) 返回一个元组,因此请注意,您不能args直接变异(您必须将可变对象拉出args)。
(b)编写装饰器和闭包
*args第二个出现的地方是装饰器。为此,ParamSpec按照中所述的方式使用PEP 612是可行的方法。
(c)调用助手的顶级函数
这就是我提到的“ Python 特定的设计模式”。对于Python >= 3.11,python 文档显示了示例,您可以在其中TypeVarTuple键入此内容,以便在调用之间保留类型信息。
*args这种方式通常是为了减少要编写的代码量,尤其是。当多个函数之间的参数相同时在这里, 中的项目*args具有异构类型,并且可能具有可变数量,这两者都可能是有问题的。
Python 类型生态系统没有办法指定异构*args. 1
在类型检查出现之前,如果开发人员需要根据类型执行不同的操作,则需要检查*args(使用assert、等)中各个参数的类型:isinstance
例子:
strs,但对传递的ints求和值得庆幸的是,mypy开发人员包括类型推断和类型缩小来mypy支持这些类型的情况。(此外,如果现有代码库已经使用assert、isintance等来确定 中项目的类型,则*args不需要进行太多更改)
因此,在这种情况下,您将执行以下操作:
*args类型object,使其元素可以是任何类型,并且assert ... is (not) None,isinstance用 、issubclass、 等来确定单个项目的类型*args1警告:
对于
Python >= 3.11,*args可以使用 来键入TypeVarTuple,但这意味着在类型提示可变参数泛型时使用 。一般情况下不应将其用于打字。*args
TypeVarTuple主要是为了帮助类型提示numpy数组、tensorflow张量和类似的数据结构而引入的,但是对于Python >= 3.11,它可以用于在调用帮助程序的顶级函数之间保留类型信息,如前所述。处理异构的函数
*args(不仅仅是传递它们)仍然必须输入窄类型以确定单个项目的类型。对于
Python <3.11,TypeVarTuple可以通过 访问 ,但迄今为止仅通过(不是)typing_extensions对其提供临时支持。此外,还包括有关用作类型变量元组的部分。pyrightmypyPEP 646*args
**kwargs(a)对数量可变的同质参数进行运算
PEP 484支持将字典的所有值**kwargs键入为同类类型。所有键均自动str按下。
与 一样*args,也可以使用Unions、TypeAliass、Generics 和Protocols 作为 的类型*kwargs。
我还没有找到一个令人信服的用例来使用**kwargs.
(b)编写装饰器和闭包
我再次向您指出,ParamSpec如 中所述PEP 612。
(c)调用助手的顶级函数
这也是我提到的“ Python 特定的设计模式”。
对于一组有限的异构关键字类型,如果PEP 692获得批准,您可以使用TypedDictand 。Unpack
然而,同样的事情也*args适用于这里:
object如果您的类型是异构的且大小未知,请在函数体中键入hint并键入narrow(c)这最终相当于遵循中各部分的指南Case 1。
您问题的答案还取决于您使用的静态类型检查器。迄今为止(据我所知),您对静态类型检查器的选择包括:
mypy:Python事实上的静态类型检查器pyright:微软的静态类型检查器pyre:Facebook/Instagram 的静态类型检查器pytype: Google 的静态类型检查器我个人只使用过mypy和pyright。对于这些,mypy游乐场和pyright游乐场是测试代码暗示类型的好地方。
ABC 与描述符和元类一样,是构建框架的工具 (1)。如果您有机会将 API 从“成人同意”的 Python 语法转变为“束缚和纪律”语法(借用Raymond Hettinger 的短语),请考虑YAGNE。
也就是说(抛开说教不谈),在编写接口时,重要的是要考虑是否应该使用Protocols 还是ABCs。
在 OOP 中,协议是一种非正式接口,仅在文档中定义,而不是在代码中定义(请参阅Luciano Ramalho 撰写的 Fluent Python,第 11 章的这篇评论文章)。Python 从 Smalltalk 中继承了这个概念,其中协议是一个接口,被视为一组要实现的方法。在 Python 中,这是通过实现特定的 dunder 方法来实现的,该方法在Python 数据模型中进行了描述,我在这里简要介绍一下。
协议实现了所谓的结构子类型。在此范例中,_a 子类型由其结构(即行为)确定,这与名义子类型(即子类型由其继承树确定)相反。与传统(动态)鸭子类型相比,结构子类型也称为静态鸭子类型。(这个术语要感谢 Alex Martelli。)
其他类不需要子类化来遵守协议:它们只需要实现特定的 dunder 方法。通过类型提示, Python 3.8 中的PEP 544引入了一种形式化协议概念的方法。现在,您可以创建一个类,继承Protocol并定义您想要的任何函数。只要另一个类实现了这些功能,就被认为遵守该功能Protocol。
抽象基类补充了鸭子类型,并且在遇到以下情况时很有帮助:
class Artist:
def draw(self): ...
class Gunslinger:
def draw(self): ...
class Lottery:
def draw(self): ...
Run Code Online (Sandbox Code Playgroud)
在这里,这些类都实现了 might 的事实draw()并不一定意味着这些对象是可以互换的(再次参见 Fluent Python,第 11 章,作者:Luciano Ramalho)!ABC 使您能够做出明确的意图声明。另外,您可以通过ing 类来创建虚拟子类register,这样您就不必从它派生子类(从这个意义上说,您遵循 GoF 的“优先于组合而不是继承”原则,而不是直接将自己与 ABC 联系起来)。
Raymond Hettinger 在他的PyCon 2019 Talk中的集合模块中就 ABC 进行了精彩的演讲。
此外,Alex Martelli 将 ABC 称为鹅打字。您可以对 中的许多类进行子类化collections.abc,仅实现少数方法,并使类的行为类似于使用 dunder 方法实现的内置 Python 协议。
Luciano Ramalho 在他的PyCon 2021 Talk中对此及其与打字生态系统的关系进行了精彩的演讲。
@overload@overload旨在用于模拟功能多态性。
Python 本身并不支持函数多态性(C++ 和其他几种语言支持)。
def函数具有多个签名,则最后一个函数def将覆盖(重新定义)前面的函数。def func(a: int, b: str, c: bool) -> str:
print(f'{a}, {b}, {c}')
def func(a: int, b: bool) -> str:
print(f'{a}, {b}')
if __name__ == '__main__':
func(1, '2', True) # Error: `func()` takes 2 positional arguments but 3 were given
Run Code Online (Sandbox Code Playgroud)
Python 通过可选的位置/关键字参数模仿功能多态性(巧合的是,C++ 不支持关键字参数)。
当以下情况时应使用重载:
请参阅Adam Johnson 的博客文章“Python 类型提示 - 如何使用@overload.
(1) 拉马略,卢西亚诺。流利的 Python(第 320 页)。奥莱利媒体。Kindle版。
在某些情况下,**kwargs 的内容可以是多种类型。
这似乎对我有用:
from typing import Any
def testfunc(**kwargs: Any) -> None:
print(kwargs)
Run Code Online (Sandbox Code Playgroud)
或者
from typing import Any, Optional
def testfunc(**kwargs: Optional[Any]) -> None:
print(kwargs)
Run Code Online (Sandbox Code Playgroud)
如果您觉得需要限制类型,**kwargs我建议创建一个类似结构的对象并在其中添加类型。这可以通过数据类或 pydantic 来完成。
from dataclasses import dataclass
@dataclass
class MyTypedKwargs:
expected_variable: str
other_expected_variable: int
def testfunc(expectedargs: MyTypedKwargs) -> None:
pass
Run Code Online (Sandbox Code Playgroud)
如果想要描述 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)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
31446 次 |
| 最近记录: |