The future of type annotations in Python

Background

PEP 3107

One of the innovations in Python 3.0 was the introduction of a new syntax that allows you to store arbitrary metadata of arguments and return values โ€‹โ€‹of functions as annotations. The innovation was described in  PEP 3107 .





According to him, the function annotations are:





  • completely optional to use;





  • a way to associate arbitrary Python expressions with various parts of a function at the time the interpreter defines it.





For Python, annotations are of no interest and are not used in any way. It just makes annotations available to third party tools.





These tools can use them as they please. For example, to add additional descriptions to the function arguments:





def func(arg: "arg description"):
    pass

      
      



Or to provide type checking for arguments and return values โ€‹โ€‹of functions:





def func(arg: int) -> bool:
    pass

      
      



PEP 3107 emphasized that the examples above are by themselves meaningless. They only make sense for third-party tools such as static analyzers, etc.





Annotations can be accessed using a special dictionary  __annotations__



:





def func(arg: "arg description"):
    pass

print("Annotations: ", func.annotations)

      
      



Let's run the script:





$ python ./example.py

Annotations: {"arg": "arg description"}

      
      



PEP 484 - Type Hints

PEP 3107 deliberately did not define the semantics of function annotations and how to use them.





,     . Python .





Python 3.5  PEP 484, . , .





PEP 484  typing



, .





, (  mypy  PEP 484), ,  __annotations__



. .





PEP. , ,  typing



, Python. , Python 3.6  Dict[str, Tuple[S, T]]



, Python 3.5 . Python 3.6, - Python 3.5.





, ,    (forward reference).





class Tree:
    def __init__(self, height: int, children: List[Tree]):
        self.height = height
        self.children = children

      
      



, . , .  Tree



, ,  NameError



   name 'Tree' is not defined



.





, , , , . PEP 484 :





class Tree:
    def __init__(self, height: int, children: List['Tree']):
        self.height = height
        self.children = children

      
      



, ,  eval



  Python.





, , :





  • Python;





  • , ;





  • , , .





: , , , .





PEP 563 -

PEP 563  :





  • ( );





  • , , .





:





class Tree:
    def __init__(self, height: int, children: List[Tree]):
        self.height = height
        self.children = children

      
      



, PEP . , :





class Tree:
    def __init__(self, height: "int", children: "List[Tree]"):
        self.height = height
        self.children = children

      
      



, ,  __annotations__



, .





, , .





, Python,  typing.get_type_hints



.





:





  • Python;





  • , , .





PEP Python 3.7, ,  from __future__ import annotations



. Python 3.10.





, PEP 563, . . , Python,  typing.get_type_hints



.





, , . ,  typing.get_type_hints



  , , , .





, (, , ..). : , ,  typing.get_type_hints



.





"" Python 3.10  Pydantic,  issue  Github, ,  Pydantic  . , , ,  Pydantic   FastAPI.





, , Python .  PEP 649.





issue, Python Pydantic FastAPI . , - .





Python , Python 3.11. . PEP 649 Python 3.11.





PEP 649 -

Python 3.9 :





  • , (PEP 484);





  • (PEP 563).





, . , , , .





PEP 649 , . , , .





, , . , __annotations__



  , .  __annotations__



, , , .  __annotations__



  , , .





, , , . . , ,  __annotations__



.





, PEP 563.





:





def foo(x: int = 3, y: MyType = None) -> float:
    ...

class MyType:
    ...
foo_y_type = foo.annotations['y']

      
      



 __annotations__



 , . , __annotations__



  .





Python 3.9 , , . , , :





annotations = {'x': int, 'y': MyType, 'return': float}

def foo(x = 3, y = "abc"):
    ...
foo.annotations = annotations
class MyType:
    ...
foo_y_type = foo.annotations['y']

      
      



, . , PEP 563 :





annotations = {'x': 'int', 'y': 'MyType', 'return': 'float'}

def foo(x = 3, y = "abc"):
    ...
foo.annotations = annotations
class MyType:
    ...
foo_y_type = foo.annotations['y']

      
      



, . , :





class function:
    #  __annotations__    "
    # ",     .
    @property
    def __annotations__(self):
        return self.__co_annotations__()

# ...
def foo_annotations_fn():
    return {'x': int, 'y': MyType, 'return': float}
def foo(x = 3, y = "abc"):
    ...
foo.co_annotations = foo_annotations_fn
class MyType:
   ...
foo_y_type = foo.annotations['y']

      
      



, , , ,  foo_annotations_fn()



. ,  foo.__annotations__



, ,  MyType



. , ,  foo_y_type



  -  MyType



 - ,  MyType



  , .





At the moment, the problem of forward links remains unresolved. A solution that is suitable for static code analysis is completely unsuitable for use at runtime. The community of developers using  Pydantic  and  FastAPI  continues to grow ( FastAPI  ranks third among Python web frameworks) and Python developers will have to take their opinion into account when developing annotations.





Despite some uncertainty, I think there will be even more ways to use annotations at runtime in the future. Thanks for reading!








All Articles