如何在运行时根据 Union 类型检查变量?

问题描述 投票:0回答:5

我正在尝试编写一个函数装饰器,它使用 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]
,但如果不使用检查
bark.__annotations__['descr'].__repr__()
的丑陋技巧,我无法在运行时访问类型列表。

有正确的方法来访问这些信息吗?或者是故意不让运行时容易访问?

python python-3.x annotations typechecking union-types
5个回答
90
投票

在 Python 3.8 及更高版本中,可以通过不使用未记录的属性 __origin____args__ 来改进

MSeifert
Richard 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

33
投票

您可以使用

__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
更好的方法。


25
投票

MSeifert 现有接受的答案(https://stackoverflow.com/a/45959000/7433423)无法将

Union
与其他泛型类型区分开来,并且很难在运行时确定类型注释是否是
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

9
投票

您可以使用

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 可能会发生更改。


-1
投票

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

但是我们可以简单地使用

type(Union)
来请求类型 (也适用于 2.7)

>>> from typing import Union
>>> type(Union)
typing.Union
>>> x = Union[int, str]
>>> isinstance(x, type(Union))
True
© www.soinside.com 2019 - 2024. All rights reserved.