【问题标题】:Check a variable against Union type at runtime in Python 3.6在 Python 3.6 中在运行时根据联合类型检查变量
【发布时间】:2018-02-08 00:04:04
【问题描述】:

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

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

是否有适当的方法来访问这些信息?还是故意让它在运行时不易访问?

【问题讨论】:

标签: python annotations python-3.6 typechecking


【解决方案1】:

在 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

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

【讨论】:

  • 正是我需要的。
  • 只是一个旁注 - 可以从 typing_extensions 导入它,它也将在 3.7 上工作
【解决方案2】:

您可以使用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__ 参数没有被记录,因此它可能被认为是“搞乱了实现细节”,但它似乎比解析 repr 更好。

【讨论】:

  • Python 3.5 使用 Union[int, str].__union_args__ 代替,在 3.6 中似乎无法从任何其他 Generic 解析 Union,因为 isinstance 失败:(
【解决方案3】:

MSeifert (https://stackoverflow.com/a/45959000/7433423) 现有接受的答案无法将Unions 与其他泛型类型区分开来,并且在运行时很难确定类型注释是Union 还是其他诸如@ 的泛型类型987654324@ 由于 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

【讨论】:

    【解决方案4】:

    您可以使用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 可能会发生变化。

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

      • 这不会进行所需的检查 - 它应该返回 True iff xintstr,而不是如果 xUnion 类型对象本身。
      猜你喜欢
      • 2012-01-27
      • 2021-10-26
      • 2010-10-02
      • 1970-01-01
      • 2017-09-24
      • 2020-08-04
      • 2023-01-24
      • 2013-12-11
      • 1970-01-01
      相关资源
      最近更新 更多