PEP 3107 (Feature Annotations)

Hello everyone. I decided to fully understand the Python annotations and at the same time translate a series of PEPs documenting this topic. We will start with the 3.X standards and end with the innovations in python 3.8. I must say right away that this PEP is one of the most basic and its reading is only useful for beginners. Well, let's go:


PEP 572 - Feature Annotations

Pep3107
Title:Feature Annotations
Authors:Collin Winter <collinwinter at google.com>, Tony Lownds <tony at lownds.com>
Status:Final
A type:Standard
Created:2-Dec-2006
Python version:3.0

Annotation to the standard


This PEP introduces syntax for adding arbitrary annotations (metadata) to functions in Python.

Justification


Functions in Python 2.x did not have a built-in way to annotate parameters and return values. To address this gap, many tools and libraries have appeared. Some of them use the decorators described in PEP 318, while others analyze docstring functions and look for information there.

This PEP aims to provide a single, standard way to annotate functions in order to reduce the confusion that has existed up to this point, caused by the wide variation in mechanisms and syntax.

Feature Annotation Basics


Before we start discussing Python 3.0 feature annotations, let's first talk about their features in general terms:

  1. Annotations for parameters and return values ​​of functions are completely optional.
  2. Annotations are nothing more than a way to associate arbitrary expressions with different parts of a function at compile time.

    Python itself does not pay any attention to annotations. The only thing is that it allows you to access them, which is described in the "Getting Access to Feature Annotations" section below.

    Annotations only make sense when interpreted by third-party libraries. They can do whatever they want with function annotations. For example, one library may use string annotations to provide improved reference data, for example:

    def compile(source: "something compilable",
                filename: "where the compilable thing comes from",
                mode: "is this a single statement or a suite?"):
        ...

    Another library may use Python function and method annotations to check for type matching. This library can use annotations to show what types of input data it expects and what type of data it will return. Perhaps it will be something like this:

    def haul(item: Haulable, *vargs: PackAnimal) -> Distance:
        ...

    We repeat once again: the annotations of none of the examples in themselves have any meaning. They acquire real meaning only in combination with third-party libraries.
  3. As follows from paragraph 2, this PEP does not attempt to introduce any standard mechanism for processing annotations even for built-in types. All this work is assigned to third-party libraries.

Syntax


Parameters


Parameter annotations are optional expressions following the name of the parameter itself:

def foo (a: , b:  = 5):
    ...

In pseudogrammatics, parameters now look like: parameter [: expression] [= expression] . That is, annotations, like default values, are optional and always precede the latter. Just like an equal sign is used to indicate a default value, a colon is used for annotations. All annotation expressions, like default values, are evaluated when a function definition is executed. Getting "extra" arguments (ie * args and ** kwargs) has the same syntax:

def foo(*args: expression, **kwargs: expression):
    ...

Annotations for nested parameters always follow the parameter name, not the last bracket. Annotation of each “name” in the nested parameter is not required:

def foo((x1, y1: expression),
        (x2: expression, y2: expression)=(None, None)):
    ...

Return values


So far, we have not provided an example of how to annotate the return type in functions. This is done like this:

def sum() -> expression:
    ...

That is, the literal -> and some kind of expression can now follow the list of parameters. Like parameter annotations, this expression will be evaluated when the function definition is executed.

The full grammar of function declarations is now as follows:

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorators: decorator+
funcdef: [decorators] 'def' NAME parameters ['->' test] ':' suite
parameters: '(' [typedargslist] ')'
typedargslist: ((tfpdef ['=' test] ',')*
                ('*' [tname] (',' tname ['=' test])* [',' '**' tname]
                 | '**' tname)
                | tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
tname: NAME [':' test]
tfpdef: tname | '(' tfplist ')'
tfplist: tfpdef (',' tfpdef)* [',']

Lambdas


Lambda functions do not support annotations. Of course, the syntax could be corrected by adding the ability to "wrap" the arguments in brackets, but it was decided not to make such a change, because:

  1. This would violate backward compatibility.
  2. Lambdas are neutral and anonymous by definition
  3. Lambdas can always be rewritten as functions


Get access to feature annotations


After compilation, function annotations become available through the __annotations__ attribute. This attribute is a mutable dictionary comparing the names of variables and the values ​​of the annotations indicated to them.

The __annotations__ dictionary has a special "return" key. This key is present only if an annotation has also been defined for the return value of the function. For example, the following annotation:

def foo (a: 'x', b: 5 + 6, c: list) -> max (2, 9):
    ...

Will be returned via the __annotations__ attribute as:

{'a': 'x',
 'b': 11,
 'c': list,
 'return': 9}

The key name “return” was chosen because it cannot conflict with the parameter name (any attempt to use return as the parameter name will result in a SyntaxError exception).

The __annotations__ attribute will be empty if the function does not have annotations or if the function was created through a lambda expression.

Use cases


During the discussion of annotations, we examined several options for their use. Some of them are presented here and grouped according to the "class" of information that is transmitted. Also included here are examples of existing products and packages that can use annotation.

  • Providing Type Information
    • Type check
    • IDE Tips on Expected and Returned Argument Types
    • Function overloading / generic functions [approx. function overloading is popular in other languages ​​and consists in the existence of several functions with the same names, but at the same time, the number of parameters accepted by them varies]
    • Bridges between different programming languages
    • Adaptation
    • Predicate logic functions
    • Mapping a database query
    • RPC parameter marshaling [approx. Marshaling is the process of converting information stored in RAM into a format suitable for storage or transmission. RPC - Remote Procedure Call]
  • Providing other information
    • Documenting parameters and return values

Standard library


Pydoc and inspect modules


The pydoc module will display annotations in the function reference information. The inspect module will change and will support annotations.

Link to other PEPs


Function Signature Objects


Function signature objects must provide function annotations. Parameter object and other things may change. [approx. Signature (Signature) of the function - part of its general declaration, allowing the means of translation to identify the function among others]

Implementation


The reference implementation was included in the py3k branch (formerly “p3yk”) as revision 53170

Rejected offers


  • BDFL [. ] , , : « »
  • stdlib , , . .
  • , , .
  • Despite further discussion, it was decided not to standardize the mechanism for annotation interaction. Such an agreement at this stage would be premature. We want to allow these agreements to develop organically, based on a real situation, and not try to force everyone to use some kind of contrived scheme.

All Articles