在 Python 3.6 中在运行时根据 Union 类型检查变量
Check a variable against Union type at runtime in 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
我能做到:
isinstance('Arnold', bark.__annotations__['myname'])
但不是:
isinstance(3, bark.__annotations__['descr'])
因为Union
不能与isinstance
或issubclass
一起使用。
我找不到使用类型对象检查它的方法。
我试图自己实施检查,但是 bark.__annotations__['descr']
在 REPL 中显示为 typing.Union[int, str]
如果不使用检查 [=20 的丑陋技巧,我无法在运行时访问类型列表=].
是否有访问此信息的正确方法?还是故意让它在运行时不易访问?
您可以使用 Union
的 __args__
属性,其中包含 tuple
的“可能内容:
>>> from typing import Union
>>> x = Union[int, str]
>>> x.__args__
(int, str)
>>> isinstance(3, x.__args__)
True
>>> isinstance('a', x.__args__)
True
__args__
参数没有记录,因此可以考虑 "messing with implementation details" 但它似乎是比解析 repr
.
更好的方法
MSeifert () 现有接受的答案没有将 Union
s 与其他泛型类型区分开来,并且很难在运行时确定类型注释是否是 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
您可以使用 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
如果因为其他原因想要检查单个变量的类型,可以直接使用typeguard的check_type
函数:
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
本例中未使用 "arg"
和 None
参数。
请注意,check_type
函数未记录为此模块的 public 函数,因此其 API 可能会发生变化。
在 Python 3.8 及更高版本中, and can be improved by not using the undocumented attributes __origin__
and __args__
. This functionality is provided by the new functions typing.get_args(tp)
and 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
P.S.: 我知道问题是关于Python 3.6(可能是因为这是当时最新的版本),但是当我搜索解决方案作为Python 3.8 用户。我想其他人可能也有同样的情况,所以我认为在这里添加一个新的答案是有道理的。
我猜 Union 本身不是类型,而是对类型的描述。
但是我们可以简单地通过使用 type(Union)
来请求类型
(也适用于 2.7)
>>> from typing import Union
>>> type(Union)
typing.Union
>>> x = Union[int, str]
>>> isinstance(x, type(Union))
True
我正在尝试编写一个函数装饰器,它使用 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
我能做到:
isinstance('Arnold', bark.__annotations__['myname'])
但不是:
isinstance(3, bark.__annotations__['descr'])
因为Union
不能与isinstance
或issubclass
一起使用。
我找不到使用类型对象检查它的方法。
我试图自己实施检查,但是 bark.__annotations__['descr']
在 REPL 中显示为 typing.Union[int, str]
如果不使用检查 [=20 的丑陋技巧,我无法在运行时访问类型列表=].
是否有访问此信息的正确方法?还是故意让它在运行时不易访问?
您可以使用 Union
的 __args__
属性,其中包含 tuple
的“可能内容:
>>> from typing import Union
>>> x = Union[int, str]
>>> x.__args__
(int, str)
>>> isinstance(3, x.__args__)
True
>>> isinstance('a', x.__args__)
True
__args__
参数没有记录,因此可以考虑 "messing with implementation details" 但它似乎是比解析 repr
.
MSeifert (Union
s 与其他泛型类型区分开来,并且很难在运行时确定类型注释是否是 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
您可以使用 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
如果因为其他原因想要检查单个变量的类型,可以直接使用typeguard的check_type
函数:
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
本例中未使用 "arg"
和 None
参数。
请注意,check_type
函数未记录为此模块的 public 函数,因此其 API 可能会发生变化。
在 Python 3.8 及更高版本中,__origin__
and __args__
. This functionality is provided by the new functions typing.get_args(tp)
and 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
P.S.: 我知道问题是关于Python 3.6(可能是因为这是当时最新的版本),但是当我搜索解决方案作为Python 3.8 用户。我想其他人可能也有同样的情况,所以我认为在这里添加一个新的答案是有道理的。
我猜 Union 本身不是类型,而是对类型的描述。
但是我们可以简单地通过使用 type(Union)
来请求类型
(也适用于 2.7)
>>> from typing import Union
>>> type(Union)
typing.Union
>>> x = Union[int, str]
>>> isinstance(x, type(Union))
True