Unpacking Python's Type Annotations
List
is not a map of types to GenericMeta
, despite the syntax. Each access to it generates a new instance:
>>> [ id(List[str]) for i in range(3) ]
[33105112, 33106872, 33046936]
This means that even List[int] is not List[int]
. To compare two instances, you have multiple options:
- Use
==
, i.e.,signature.return_annotation == List[int]
. Store an instance of your type in a global variable and check against that, i.e.,
a = List[int] def foo() -> a: pass inspect.signature(foo).return_annotation is a
Use
issubclass
. The typing module defines that. Note that this might do more than you'd like, make sure to read the_TypeAlias
documentation if you use this.- Check against
List
only and read the contents yourself. Though the property is internal, it is unlikely that the implementation will change soon:List[int].__args__[0]
contains the type argument starting from Python 3.5.2, and in earlier versions, itsList[int].__parameters__[0]
.
If you'd like to write generic code for your exporter, then the last option is probably best. If you only need to cover a specific use case, I'd personally go with using ==
.
Take note, this applies to Python 3.5.1
For Python 3.5.2 take a look at phillip's answer.
You shouldn't be checking with the identity operator as Phillip stated, use equality to get this right.
To check if a hint is a subclass of a list
you could use issubclass
checks (even though you should take note that this can be quirky in certain cases and is currently worked on):
issubclass(List[int], list) # True
To get the members of a type hint you generally have two watch out for the cases involved.
If it has a simple type, as in List[int]
the value of the argument is located in the __parameters__
value:
signature.return_annotation.__parameters__[0] # int
Now, in more complex scenarios i.e a class supplied as an argument with List[User]
you must again extract the __parameter__[0]
and then get the __forward_arg__
. This is because Python wraps the argument in a special ForwardRef
class:
d = signature.return_annotation.__parameter__[0]
d.__forward_arg__ # 'User'
Take note, you don't need to actually use inspect
here, typing
has a helper function named get_type_hints
that returns the type hints as a dictionary (it uses the function objects __annotations__
attribute).
Python 3.8 provides typing.get_origin()
and typing.get_args()
for this!
assert get_origin(Dict[str, int]) is dict
assert get_args(Dict[int, str]) == (int, str)
assert get_origin(Union[int, str]) is Union
assert get_args(Union[int, str]) == (int, str)
See https://docs.python.org/3/library/typing.html#typing.get_origin