在Python 3.6中,在运行时检查Union类型的变量

时间:2017-08-30 10:10:09

标签: python annotations python-3.6 typechecking

我正在尝试编写一个函数装饰器,它使用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无法与isinstanceissubclass一起使用。

我找不到使用type对象检查它的方法。 我试图自己实现检查,但是当bark.__annotations__['descr']在REPL中显示为typing.Union[int, str]时,我无法在运行时访问类型列表,如果不使用丑陋的黑客检查{{1 }}

是否有正确的方法来访问此信息?或者是故意在运行时不容易访问它?

5 个答案:

答案 0 :(得分:7)

在Python 3.8和更高版本中,可以通过不使用未记录的属性__origin____args__来改进MSeifertRichard Xia建议的方法。新功能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

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

答案 1 :(得分:5)

MSeifert(Remove Item from Firebase database)现有的已接受答案并未将Union与其他泛型类型区分开来,并且很难在运行时确定类型注释是Union还是由于Mappingisinstance()在参数化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

答案 2 :(得分:5)

您可以使用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函数未记录为该模块的公共函数,因此其API可能会更改。

答案 3 :(得分:4)

您可以使用__args__ Union属性,该属性包含tuple个“可能的内容:

>>> from typing import Union

>>> x = Union[int, str]
>>> x.__args__
(int, str)
>>> isinstance(3, x.__args__)
True
>>> isinstance('a', x.__args__)
True

__args__参数没有记录,所以它可以被认为是“搞乱实现细节”,但它似乎比解析repr更好。

答案 4 :(得分:0)

我猜Union本身不是一个类型,而是一个类型的描述。

但是我们可以简单地使用 type(Union) 来请求类型 (也适用于 2.7)

>>> from typing import Union
>>> type(Union)
typing.Union
>>> x = Union[int, str]
>>> isinstance(x, type(Union))
True