Search code examples
pythontype-hintingdocstring

Python convert type hint string representation (from docstring) to actual type/typing object


I would like to know if there is an existing implementation that converts the string representation of a type hint (e.g. from a docstring) back to the type (or typing) object. Thereby arbitrary deep nested types should also be supported (see third example).

import typing import List, Optional, Union

converter_function("List[str]") == List[str]

converter_function("int") == int

converter_function("Optional[List[Union[str, float]]]") == Optional[List[Union[str,float]]]

converter_function("foobar") # Exception / None

One option would be to simply use eval, but I would like to avoid that at all costs :D

Edit As background on why I want to do this: I want to write a script that checks if a docstring matches 100% to a function, all types are correct and so on.


Solution

  • The closest thing I can find (except for eval of course) is typing.get_type_hints. If your functions are properly type annotated, then typing.get_type_hints can resolve the annotations. It works for string annotations, arbitrarily nested types, and generic types; basically any valid annotations. For example:

    from typing import List, Optional, Union, get_type_hints
    
    def my_func(a: "List[str]", b: "int") -> "Optional[List[Union[str, float]]]":
        pass
    
    print(get_type_hints(my_func))
    

    you'd get

    {'a': typing.List[str],
     'b': <class 'int'>,
     'return': typing.Optional[typing.List[typing.Union[str, float]]]}
    

    Internally, get_type_hints converts the strings into typing.ForwardRefs and then evaluate them. These are private APIs so they might not be stable across Python versions, but if you only have the annotation strings but not a function, you might try this out:

    >>> from typing import ForwardRef, _eval_type
    
    >>> _eval_type(ForwardRef("Optional[List[Union[str, float]]]"), globals(), globals())
    typing.Optional[typing.List[typing.Union[str, float]]]
    

    but at this point, you might be better off with eval :P