Function annotations in Python

PEP: PEP stands for Python Improvement Proposal. It is a design document that describes new features for Python or its processes or environment. It also provides information for the Python community.
PEP — it is the primary mechanism for proposing major new features such as the Python web server gateway interface, gathering community information on issues, and documenting design decisions that have been implemented in Python.

Feature annotations — PEP 3107: PEP-3107 introduced the concept and syntax for adding arbitrary metadata annotations in Python. It was introduced in Python3, which was previously made using external libraries in Python 2.x

What are function annotations?

Function annotations — they are arbitrary Python expressions associated with different parts of the functions. These expressions are evaluated at compile time and have no life in the Python runtime. Python doesn`t attach any importance to these annotations. They take life when interpreted by third party libraries like Mypy.

The purpose of function annotations:
The benefits of function annotations can only be gained through third party libraries. The type of privileges depends on the type of library, for example

  1. Python supports dynamic typing and therefore there is no module for type checking. Annotations as
     [def foo (a: ”int”, b: ”float” = 5.0) - & gt; ”Int”] 

    (the syntax is detailed in the next section) can be used to gather information about the type of parameters and the return type of a function, in order to track the type change that occurs in the function. mypy — one such library.

  2. String-based libraries can be used by libraries to provide better compile-time reference messages regarding the functions of various methods, classes and modules.
  3. Functional annotation syntax

    They are like the optional parameters that follow the parameter name.

      Note . The word “expression” mentioned below can be the type of parameters to be passed, or comment, or any arbitrary string that can be used in a meaningful way by external libraries.

    1. Annotations for simple parameters: the argument name is followed by “:” followed by an expression. The annotation syntax is shown below:
        def foobar (a: expression, b: expression = 5): 
    2. Annotations for redundant parameters: redundant parameters, for example, for * args and ** kwargs, allow you to pass an arbitrary number of arguments in a function call. The syntax for annotating such parameters is as follows:
        def foobar (* args: expression, * kwargs: expression): 
    3. Annotations for nested parameters: Nested parameters — A useful feature of Python 2x where a tuple is passed in when a function is called and auto-unpacking occurs. This feature has been removed in Python 3x and must be manually unpacked. The annotation is after the variable, not after the tuple, as shown below.
        def foobar ((a: expression, b: expression), (c: expression, d: expression)):   
    4. Annotated return type: The annotated return type is slightly different from the annotated function arguments. & # 39; - & gt; & # 39; followed by an expression followed by & # 39;: & # 39 ;. The return type annotation syntax is shown below:
        def foobar (a: expression) - & gt; expression:  

    grammar

      decorator : `@` name_ [`(` [arglist] `)`] NEWLINE  decorators : decorator +  funcdef : [decorators] `def` NAME parameters [`- & gt;`] `:` suite  parameters : `(` [typedarglist] `)`  typedarglist : ( (tfpdef [`=` test] `,`) * (`*` [tname] (`,` tname [`=` test]) * [`,` `**` tname] | `**` tname) | tfpdef [`=` test (`,` tfpdef [`=` test]) * [`,`]])  tname : NAME [`:` test]  tfpdef : tname | `(` tfplist `)`  tfplist : tfpdef (`,` tfpdef) * [`,`] 

    Grammar visualization: parse tree generated from the above grammar to provide a better visualization of function syntax and Python annotations.

    Sample Code

    Below the code will clarify the fact that function annotations are not evaluated at runtime. The code prints the Fibonacci series up to the "n" positions.

    # Python program for printing Fibonacci series

    def fib (n: `int` , output: `list` = [] ) - & gt; `list` :

      if n = = 0 :

    return output

    else :

    if len (output) & lt; 2 :

    output.append ( 1 )

    fib (n - 1 , output)

    else :

    last = output [ - 1 ]

    second_last = output [ - 2 ]

      output.append (last + second_last)

    fib (n - 1 , output)

      return output

    print (fib ( 5 ))

     Output: [1, 1, 2, 3, 5] 

    Note: Function annotations are only supported in Python 3x.

    Access to function annotations

    1. Using "__annotations__". The function annotations in the above code are accessed through the special attribute "__annotations__". It outputs a dictionary with the special key return and other keys with the names of the annotated arguments. The following code will print annotations.

    # Python program to illustrate function annotations

    def fib (n: `int` , output: `list` = []) - & gt; `list` :

      if n = = 0 :

    return output

    else :

    if len (output) & lt; 2 :

    output.append ( 1 )

    fib (n - 1 , output)

    else :

    last = output [ - 1 ]

    second_last = output [ - 2 ]

      output.append (last + second_last)

    fib (n - 1 , output)

      return output

    print (fib .__ annotations__ )

     Output: {`return`:` list`, `n`:` int`, `output`:` list`} 

    2. Using the standard module & # 39; pydoc & # 39; : & # 39; pydoc & # 39; — it is a standard python module that returns documentation inside a python module (if any). It has a special method help () that provides an interactive shell for getting help for any keyword, method, class, or module. & # 39; help () & # 39; can be used to access function annotations. The figure below shows the function annotations in the above Fibonacci series code. Module name — "Fib.py".

    3. Using the standard inspect module : The inspect module provides several useful functions that help you get information about living objects, such as modules, classes, methods, functions, callbacks, frame objects, and code objects. We can use the getfullargspec method of the module to get full information about the function, which will contain annotations.

    # Python program to illustrate function annotations

    import inspect

    def fib (n: `int` , output: `list` = []) - & gt; `list` :

      if n = = 0 :

    return output

    else :

    if len (output) & lt; 2 :

    output.append ( 1 )

    fib (n - 1 , output)

    else :

    last = output [ - 1 ]

    second_last = output [ - 2 ]

      output.append (last + second_last)

    fib (n - 1 , output)

      return output

    print (inspect.getfullargspec (fib))

     Output: FullArgSpec (args = [` n`, `output`], varargs = None, varkw = None, defaults = ([],), kwonlyargs = [], kwonlydefaults = None, annotations = {` output`: `list`,` return`: `list `,` n`: `int`}) 

    Using function annotations

    • Using & # 39; mypy & # 39 ;: & # 39; mypy & # 39; — it is an external library that provides static type checking using function annotations.
      Download Mypy for Python 2x
       pip install mypy 

      Python 3x

       pip install git + git: //github.com/JukkaL/mypy.git  

      Example 1:

      # Line slice function that returns a string from start index to end index.

      def slice (string: str , start: int , end: int ) - & gt; str :

      return string [start: end]

        

      slice ([ 1 , 2 , 3 , 4 , 5 ], 2 , 4 )

      Save the above code as example.py and run the following command after installing mypy. Make sure you are in the directory where you saved the file.

       mypy example.py 

      You get the following result.

    • Things are a little different when decorators are involved.
      Example 2 (part a): checking the types of parameters of collapsed functions & # 39; gift_func & # 39; and & # 39; wrapped & # 39;

      def wrapping_paper (func):

      def wrapped (gift: int ):

      return ` I got a wrapped up {} for you` . format ( str (func (gift)))

      return wrapped

       
      @ wrapping_paper

      def gift_func (giftna me: int ):

      return giftname

       

      print (gift_func ( `gtx 5000` ))

      At first it seems that passing a string as an argument will return an error, since the required data type is — it`s an int annotated with gift_func and wrapped. mypy does not set type checking on collapsed function parameters, however, the type checking in the decorator and the return type of collapsed functions can be checked. Therefore, the following result can be expected from the above code.

    • Example 2 (part b): checking the types of parameters of the decorator & # 39; wrapping_paper & # 39 ;.

      def wrapping_paper (func: str ): 

      def wrapped (gift: int ):

      return `I got a wrapped up {} for you` . format ( str (func (gift)))

      return wrapped

       
      @ wrapping_paper

      def gift_func (giftname: int ):

        return giftname

       

      print (gift_func ( ` gtx 5000` ))

      Now you get the following result.

    • Example 2 (part c): checking the return types of & # 39; gift_func & # 39; and & # 39; wrapped & # 39;

      # Let`s assume we want so that the return type is int

      from typing import Callable

      def wrapping_paper (func):

        def wrapped (gift) - & gt; int :

      return `I got a wrapped up {} for you` . format ( str (func (gift )))

      return wrapped

       
      @ wrapping_paper

      def gift_func (giftname) - & gt; int :

      return giftname

       

      print (gift_func ( `gtx 5000` ))

      You will get the following result.

    • Example 2 (part d) Checking the return type of a wrapper function & # 39; wrapping_paper & # 39;

      # Let`s assume we want so that the return type is int

      from typin g import Callable

      def wrapping_paper (func) - & gt; int :

      def wrapped (gift):

      return `I got a wrapped up {} for you` . format ( str (func (gift )))

      return wrapped

       
      @ wrapping_paper

      def gift_func (giftname):

      return giftname

       

      p rint (gift_func ( `gtx 5000` ))

      You will get the following result

    This article is courtesy of Anmol Chachra . If you are as Python.Engineering and would like to contribute, you can also write an article using contribute.python.engineering or by posting an article contribute @ python.engineering. See my article appearing on the Python.Engineering homepage and help other geeks.

    Please write in comments if you find anything wrong or if you`d like to share more information on the topic discussed above.