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?
Use the type() Function to Check Variable Type in Python To check the type of a variable, you can use the type() function, which takes the variable as an input. Inside this function, you have to pass either the variable name or the value itself. And it will return the variable data type.
Union type; Union[X, Y] is equivalent to X | Y and means either X or Y. To define a union, use e.g. Union[int, str] or the shorthand int | str . Using that shorthand is recommended.
Python types do not provide runtime type-checking.
It is a type variable. Type variables exist primarily for the benefit of static type checkers. They serve as the parameters for generic types as well as for generic function definitions.
In Python 3.8 and later, the approach suggested by MSeifert and Richard Xia 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.: I know that the question is about Python 3.6 (probably because this was the newest version at the time), but I arrived here when I searched for a solution as a Python 3.8 user. I guess that others might be in the same situation, so I thought that adding a new answer here makes sense.
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
.
The existing accepted answer by MSeifert (https://stackoverflow.com/a/45959000/7433423) does not distinguish Union
s 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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With