| [ Web Proxy ] |
| Viewing: https://peps.python.org/3107 | [Back] [Original] |
This PEP introduces a syntax for adding arbitrary metadata annotations to Python functions [1].
Because Pythons 2.x series lacks a standard way of annotating a functions parameters and return values, a variety of tools and libraries have appeared to fill this gap. Some utilise the decorators introduced in PEP 318, while others parse a functions docstring, looking for annotations there.
This PEP aims to provide a single, standard way of specifying this information, reducing the confusion caused by the wide variation in mechanism and syntax that has existed until this point.
Before launching into a discussion of the precise ins and outs of Python 3.0s function annotations, lets first talk broadly about what annotations are and are not:
By itself, Python does not attach any particular meaning or significance to annotations. Left to its own, Python simply makes these expressions available as described in Accessing Function Annotations below.
The only way that annotations take on meaning is when they are interpreted by third-party libraries. These annotation consumers can do anything they want with a functions annotations. For example, one library might use string-based annotations to provide improved help messages, like so:
def compile(source: "something compilable",
filename: "where the compilable thing comes from",
mode: "is this a single statement or a suite?"):
...
Another library might be used to provide typechecking for Python functions and methods. This library could use annotations to indicate the functions expected input and return types, possibly something like:
def haul(item: Haulable, *vargs: PackAnimal) -> Distance:
...
However, neither the strings in the first example nor the type information in the second example have any meaning on their own; meaning comes from third-party libraries alone.
Annotations for parameters take the form of optional expressions that follow the parameter name:
def foo(a: expression, b: expression = 5):
...
In pseudo-grammar, parameters now look like identifier [:
expression] [= expression]. That is, annotations always precede a
parameters default value and both annotations and default values are
optional. Just like how equal signs are used to indicate a default
value, colons are used to mark annotations. All annotation
expressions are evaluated when the function definition is executed,
just like default values.
Annotations for excess parameters (i.e., *args and **kwargs)
are indicated similarly:
def foo(*args: expression, **kwargs: expression):
...
Annotations for nested parameters always follow the name of the parameter, not the last parenthesis. Annotating all parameters of a nested parameter is not required:
def foo((x1, y1: expression),
(x2: expression, y2: expression)=(None, None)):
...
The examples thus far have omitted examples of how to annotate the type of a functions return value. This is done like so:
def sum() -> expression:
...
That is, the parameter list can now be followed by a literal ->
and a Python expression. Like the annotations for parameters, this
expression will be evaluated when the function definition is executed.
The grammar for function definitions [11] is now:
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 syntax does not support annotations. The syntax of
lambda could be changed to support annotations, by requiring
parentheses around the parameter list. However it was decided
[12] not to make this change because:
Once compiled, a functions annotations are available via the
functions __annotations__ attribute. This attribute is
a mutable dictionary, mapping parameter names to an object
representing the evaluated annotation expression
There is a special key in the __annotations__ mapping,
"return". This key is present only if an annotation was supplied
for the functions return value.
For example, the following annotation:
def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):
...
would result in an __annotations__ mapping of
{'a': 'x',
'b': 11,
'c': list,
'return': 9}
The return key was chosen because it cannot conflict with the name
of a parameter; any attempt to use return as a parameter name
would result in a SyntaxError.
__annotations__ is an empty, mutable dictionary if there are no
annotations on the function or if the functions was created from
a lambda expression.
In the course of discussing annotations, a number of use-cases have been raised. Some of these are presented here, grouped by what kind of information they convey. Also included are examples of existing products and packages that could make use of annotations.
The pydoc module should display the function annotations when
displaying help for a function. The inspect module should change
to support annotations.
Function Signature Objects should expose the functions annotations.
The Parameter object may change or other changes may be warranted.
A reference implementation has been checked into the py3k (formerly p3yk) branch as revision 53170 [10].
This document has been placed in the public domain.
Source: https://github.com/python/peps/blob/main/peps/pep-3107.rst
Last modified: 2025-02-01 08:59:27 UTC
| Web Proxy Viewer | New URL | Original Page |