在Python 3.6中运行时根据Union类型检查变量

Jac*_*far 8 python annotations typechecking python-3.6

我正在尝试编写一个使用Python 3.6类型提示的函数装饰器,以检查参数字典是否尊重类型提示,并且如果未出现带有问题清晰说明的错误,则将其用于HTTP API。

问题是,当函数具有使用Union类型的参数时,我无法在运行时根据其检查变量。

例如我有这个功能

from typing import Union
def bark(myname: str, descr: Union[int, str], mynum: int = 3) -> str:
    return descr + myname * mynum
Run Code Online (Sandbox Code Playgroud)

我可以:

isinstance('Arnold', bark.__annotations__['myname'])
Run Code Online (Sandbox Code Playgroud)

但不是:

isinstance(3, bark.__annotations__['descr'])
Run Code Online (Sandbox Code Playgroud)

因为Union不能与isinstance或一起使用issubclass

我找不到使用类型对象检查它的方法。我尝试自己执行检查,但是在REPL中bark.__annotations__['descr']显示typing.Union[int, str]时,如果不使用检查的丑陋技巧,我将无法在运行时访问类型列表bark.__annotations__['descr'].__repr__()

是否有访问此信息的正确方法?还是故意在运行时不易于访问它?

Fra*_*ank 33

在 Python 3.8 及更高版本中,MSeifertRichard Xia建议的方法可以通过不使用未记录的属性__origin____args__. 此功能由新功能提供,typing.get_args(tp)并且typing.get_origin(tp)

>> from typing import Union, get_origin, get_args
>> x = Union[int, str]
>> get_origin(x), get_args(x)
(typing.Union, (<class 'int'>, <class 'str'>))
>> get_origin(x) is Union
True
>> isinstance(3, get_args(x))
True
>> isinstance('a', get_args(x))
True
>> isinstance([], get_args(x))
False
Run Code Online (Sandbox Code Playgroud)

PS:我知道问题是关于 Python 3.6(可能是因为这是当时的最新版本),但是当我作为 Python 3.8 用户搜索解决方案时,我来到了这里。我猜其他人可能处于同样的情况,所以我认为在这里添加一个新答案是有道理的。

  • Python 3.10 添加了 `int | str` 语法,此解决方案不再有效。您仍然可以使用“get_origin”,但它将返回“types.UnionType”而不是“typing.Union”。因此,更通用的方法可能是 `get_origin(x) istyping.Union 或 get_origin(x) is types.UnionType` (6认同)

Ric*_*Xia 8

MSeifert(/sf/answers/3217130031/)接受的现有答案无法将Unions与其他通用类型区分开,并且很难在运行时确定类型注释是a Union还是其他通用类型,例如Mapping由于行为isinstance()issubclass()对参数Union的类型。

似乎泛型类型将具有未记录的__origin__属性,该属性将包含对用于创建它的原始泛型类型的引用。一旦确认类型注释是参数化的Union,就可以使用也未记录的__args__属性来获取类型参数。

>>> from typing import Union
>>> type_anno = Union[int, str]
>>> type_anno.__origin__ is Union
True
>>> isinstance(3, type_anno.__args__)
True
>>> isinstance('a', type_anno.__args__)
True
Run Code Online (Sandbox Code Playgroud)


Jin*_*lcl 6

您可以使用typeguard可以安装的模块pip。它为您提供了一个函数check_argument_types或函数装饰器@typechecked。哪个应该为您执行运行时类型检查:https : //github.com/agronholm/typeguard

from typing import Union
from typeguard import check_argument_types, typechecked

def check_and_do_stuff(a: Union[str, int]) -> None:
    check_argument_types() 
    # do stuff ...

@typechecked
def check_decorator(a: Union[str, int]) -> None:
    # do stuff ...

check_and_do_stuff("hello")
check_and_do_stuff(42)
check_and_do_stuff(3.14)  # raises TypeError
Run Code Online (Sandbox Code Playgroud)

如果出于其他原因要检查单个变量的类型,则可以check_type直接使用typeguard的 函数:

from typing import Union
from typeguard import check_type

MyType = Union[str, int]

check_type("arg", "string", MyType, None)  # OK
check_type("arg", 42, MyType, None)  # OK
check_type("arg", 3.5, MyType, None)  # raises TypeError
Run Code Online (Sandbox Code Playgroud)

"arg"None参数都是在这个例子中未被使用。请注意,该check_type功能未记录为该模块的公共功能,因此其API可能会更改。


MSe*_*ert 5

您可以使用其中包含“可能内容” 的__args__属性:Uniontuple

>>> from typing import Union

>>> x = Union[int, str]
>>> x.__args__
(int, str)
>>> isinstance(3, x.__args__)
True
>>> isinstance('a', x.__args__)
True
Run Code Online (Sandbox Code Playgroud)

__args__参数未记录下来,因此可以认为是“与实现细节打交道”,但它似乎是比解析更好的方法repr

  • Python 3.5使用`Union [int,str] .__ union_args__`代替,在3.6中似乎没有办法从任何其他`Generic`中解析`Union`,因为`isinstance`会失败:( (2认同)