That is exactly my use-case of stringified annotations. PEP 563 indeed makes it much more reliable.
To explain a bit: I’m writing a tool, Griffe, that visits the AST of modules to extract useful information. It is able to rebuild an expression from nodes, in which each name is a struct containing both the name as written in the code, and the full, resolved path in the scope of its parent.
For compiled/builtin modules, it cannot load an AST so it falls back on introspection. When the introspected module imports future annotations, great, I can simply compile stringified annotations to transform them into the previously mentioned expressions (these annotations come, for example, from inspect.Signature.return_annotation
). If the introspected module does not import future annotation, I have to handle an actual Python object, which is much more unpredictable.
The gist of it:
def _convert_object_to_annotation(obj, parent):
# even when *we* import future annotations,
# the object from which we get a signature
# can come from modules which did *not* import them,
# so inspect.signature returns actual Python objects
# that we must deal with
if not isinstance(obj, str):
if hasattr(obj, "__name__"):
# simple types like int, str, custom classes, etc.
obj = obj.__name__
else:
# other, more complex types: hope for the best
obj = repr(obj)
try:
annotation_node = compile(obj, mode="eval", filename="<>", flags=ast.PyCF_ONLY_AST, optimize=2)
except SyntaxError:
return obj
return get_annotation(annotation_node.body, parent=parent)
Emphasis on repr(obj)
. Types imported from the typing module have good representation methods. But this is not enough, see the example below:
>>> from typing import List, Tuple
>>> T = List[Tuple[str, int]]
>>> repr(T)
'typing.List[typing.Tuple[str, int]]'
The issue here is that typing
is unknown in the given scope, so I won’t be able to resolve it properly.
Even worse:
>>> TT = Tuple[T, T]
>>> repr(TT)
'typing.Tuple[typing.List[typing.Tuple[str, int]], typing.List[typing.Tuple[str, int]]]'
That makes a really long string, and T
was lost in the process.
If the introspected module had stringified annotations instead, I would get 'TT'
from, for example, inspect.signature
, and I would be able to resolve it.