Check a variable against Union type at runtime in Python 3.6

末鹿安然 提交于 2020-03-13 07:14:22

问题


I'm trying to write a function decorator that uses Python 3.6 type hints to check that a dictionary of arguments respects the type hints and if not raise an error with a clear description of the problem, to be used for HTTP APIs.

The problem is that when the function has a parameter using the Union type I can't check a variable against it at runtime.

For example, I have this function

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

I can do:

isinstance('Arnold', bark.__annotations__['myname'])

But not:

isinstance(3, bark.__annotations__['descr'])

Because Union cannot be used with isinstance or issubclass.

I couldn't find a way to check it using the type object. I tried to implement the check by myself but while bark.__annotations__['descr'] is shown as typing.Union[int, str] in the REPL I can't access the list of the types at runtime, if not using the ugly hack of examining bark.__annotations__['descr'].__repr__().

Is there a proper way to access this information? Or is it deliberately intended to not be easily accessible at runtime?


回答1:


You could use the __args__ attribute of Union which holds a tuple of the "possible contents:

>>> from typing import Union

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

The __args__ argument is not documented so it could be considered "messing with implementation details" but it seems like a better way than parsing the repr.




回答2:


The existing accepted answer by MSeifert (https://stackoverflow.com/a/45959000/7433423) does not distinguish Unions from other generic types, and it is difficult to determine at runtime whether a type annotation is a Union or some other generic type like Mapping due to the behavior of isinstance() and issubclass() on parameterized Union types.

It appears that generic types will have an undocumented __origin__ attribute which will contain a reference to the original generic type used to create it. Once you have confirmed that the type annotation is a parameterized Union, you can then use the also undocumented __args__ attribute to get the type parameters.

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



回答3:


You can use the typeguard module which can be installed with pip. It provides you with a function check_argument_types or a function decorator @typechecked. which should do your runtime type checking for you: 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

If you want to check a type of a single variable for a different reason, you can use typeguard's check_type function directly:

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

The "arg" and None arguments are unused in this example. Note that the check_type function is not documented as a public function of this module so its API may be subject to change.



来源:https://stackoverflow.com/questions/45957615/check-a-variable-against-union-type-at-runtime-in-python-3-6

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!