mirror of
https://github.com/python/cpython.git
synced 2025-08-22 01:35:16 +00:00
[3.14] gh-119180: More documentation for PEP 649/749 (GH-133552) (#133902)
gh-119180: More documentation for PEP 649/749 (GH-133552)
The SC asked that the Appendix in PEP-749 be added to the docs.
(cherry picked from commit 3396df56d0
)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
This commit is contained in:
parent
507715d5f7
commit
a3475e68bb
3 changed files with 141 additions and 8 deletions
|
@ -485,3 +485,117 @@ annotations from the class and puts them in a separate attribute:
|
||||||
typ.classvars = classvars # Store the ClassVars in a separate attribute
|
typ.classvars = classvars # Store the ClassVars in a separate attribute
|
||||||
return typ
|
return typ
|
||||||
|
|
||||||
|
|
||||||
|
Limitations of the ``STRING`` format
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
The :attr:`~Format.STRING` format is meant to approximate the source code
|
||||||
|
of the annotation, but the implementation strategy used means that it is not
|
||||||
|
always possible to recover the exact source code.
|
||||||
|
|
||||||
|
First, the stringifier of course cannot recover any information that is not present in
|
||||||
|
the compiled code, including comments, whitespace, parenthesization, and operations that
|
||||||
|
get simplified by the compiler.
|
||||||
|
|
||||||
|
Second, the stringifier can intercept almost all operations that involve names looked
|
||||||
|
up in some scope, but it cannot intercept operations that operate fully on constants.
|
||||||
|
As a corollary, this also means it is not safe to request the ``STRING`` format on
|
||||||
|
untrusted code: Python is powerful enough that it is possible to achieve arbitrary
|
||||||
|
code execution even with no access to any globals or builtins. For example:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> def f(x: (1).__class__.__base__.__subclasses__()[-1].__init__.__builtins__["print"]("Hello world")): pass
|
||||||
|
...
|
||||||
|
>>> annotationlib.get_annotations(f, format=annotationlib.Format.SOURCE)
|
||||||
|
Hello world
|
||||||
|
{'x': 'None'}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This particular example works as of the time of writing, but it relies on
|
||||||
|
implementation details and is not guaranteed to work in the future.
|
||||||
|
|
||||||
|
Among the different kinds of expressions that exist in Python,
|
||||||
|
as represented by the :mod:`ast` module, some expressions are supported,
|
||||||
|
meaning that the ``STRING`` format can generally recover the original source code;
|
||||||
|
others are unsupported, meaning that they may result in incorrect output or an error.
|
||||||
|
|
||||||
|
The following are supported (sometimes with caveats):
|
||||||
|
|
||||||
|
* :class:`ast.BinOp`
|
||||||
|
* :class:`ast.UnaryOp`
|
||||||
|
|
||||||
|
* :class:`ast.Invert` (``~``), :class:`ast.UAdd` (``+``), and :class:`ast.USub` (``-``) are supported
|
||||||
|
* :class:`ast.Not` (``not``) is not supported
|
||||||
|
|
||||||
|
* :class:`ast.Dict` (except when using ``**`` unpacking)
|
||||||
|
* :class:`ast.Set`
|
||||||
|
* :class:`ast.Compare`
|
||||||
|
|
||||||
|
* :class:`ast.Eq` and :class:`ast.NotEq` are supported
|
||||||
|
* :class:`ast.Lt`, :class:`ast.LtE`, :class:`ast.Gt`, and :class:`ast.GtE` are supported, but the operand may be flipped
|
||||||
|
* :class:`ast.Is`, :class:`ast.IsNot`, :class:`ast.In`, and :class:`ast.NotIn` are not supported
|
||||||
|
|
||||||
|
* :class:`ast.Call` (except when using ``**`` unpacking)
|
||||||
|
* :class:`ast.Constant` (though not the exact representation of the constant; for example, escape
|
||||||
|
sequences in strings are lost; hexadecimal numbers are converted to decimal)
|
||||||
|
* :class:`ast.Attribute` (assuming the value is not a constant)
|
||||||
|
* :class:`ast.Subscript` (assuming the value is not a constant)
|
||||||
|
* :class:`ast.Starred` (``*`` unpacking)
|
||||||
|
* :class:`ast.Name`
|
||||||
|
* :class:`ast.List`
|
||||||
|
* :class:`ast.Tuple`
|
||||||
|
* :class:`ast.Slice`
|
||||||
|
|
||||||
|
The following are unsupported, but throw an informative error when encountered by the
|
||||||
|
stringifier:
|
||||||
|
|
||||||
|
* :class:`ast.FormattedValue` (f-strings; error is not detected if conversion specifiers like ``!r``
|
||||||
|
are used)
|
||||||
|
* :class:`ast.JoinedStr` (f-strings)
|
||||||
|
|
||||||
|
The following are unsupported and result in incorrect output:
|
||||||
|
|
||||||
|
* :class:`ast.BoolOp` (``and`` and ``or``)
|
||||||
|
* :class:`ast.IfExp`
|
||||||
|
* :class:`ast.Lambda`
|
||||||
|
* :class:`ast.ListComp`
|
||||||
|
* :class:`ast.SetComp`
|
||||||
|
* :class:`ast.DictComp`
|
||||||
|
* :class:`ast.GeneratorExp`
|
||||||
|
|
||||||
|
The following are disallowed in annotation scopes and therefore not relevant:
|
||||||
|
|
||||||
|
* :class:`ast.NamedExpr` (``:=``)
|
||||||
|
* :class:`ast.Await`
|
||||||
|
* :class:`ast.Yield`
|
||||||
|
* :class:`ast.YieldFrom`
|
||||||
|
|
||||||
|
|
||||||
|
Limitations of the ``FORWARDREF`` format
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
The :attr:`~Format.FORWARDREF` format aims to produce real values as much
|
||||||
|
as possible, with anything that cannot be resolved replaced with
|
||||||
|
:class:`ForwardRef` objects. It is affected by broadly the same Limitations
|
||||||
|
as the :attr:`~Format.STRING` format: annotations that perform operations on
|
||||||
|
literals or that use unsupported expression types may raise exceptions when
|
||||||
|
evaluated using the :attr:`~Format.FORWARDREF` format.
|
||||||
|
|
||||||
|
Below are a few examples of the behavior with unsupported expressions:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> from annotationlib import get_annotations, Format
|
||||||
|
>>> def zerodiv(x: 1 / 0): ...
|
||||||
|
>>> get_annotations(zerodiv, format=Format.STRING)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ZeroDivisionError: division by zero
|
||||||
|
>>> get_annotations(zerodiv, format=Format.FORWARDREF)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ZeroDivisionError: division by zero
|
||||||
|
>>> def ifexp(x: 1 if y else 0): ...
|
||||||
|
>>> get_annotations(ifexp, format=Format.STRING)
|
||||||
|
{'x': '1'}
|
||||||
|
|
|
@ -1885,7 +1885,7 @@ expressions. The presence of annotations does not change the runtime semantics o
|
||||||
the code, except if some mechanism is used that introspects and uses the annotations
|
the code, except if some mechanism is used that introspects and uses the annotations
|
||||||
(such as :mod:`dataclasses` or :func:`functools.singledispatch`).
|
(such as :mod:`dataclasses` or :func:`functools.singledispatch`).
|
||||||
|
|
||||||
By default, annotations are lazily evaluated in a :ref:`annotation scope <annotation-scopes>`.
|
By default, annotations are lazily evaluated in an :ref:`annotation scope <annotation-scopes>`.
|
||||||
This means that they are not evaluated when the code containing the annotation is evaluated.
|
This means that they are not evaluated when the code containing the annotation is evaluated.
|
||||||
Instead, the interpreter saves information that can be used to evaluate the annotation later
|
Instead, the interpreter saves information that can be used to evaluate the annotation later
|
||||||
if requested. The :mod:`annotationlib` module provides tools for evaluating annotations.
|
if requested. The :mod:`annotationlib` module provides tools for evaluating annotations.
|
||||||
|
@ -1898,6 +1898,12 @@ all annotations are instead stored as strings::
|
||||||
>>> f.__annotations__
|
>>> f.__annotations__
|
||||||
{'param': 'annotation'}
|
{'param': 'annotation'}
|
||||||
|
|
||||||
|
This future statement will be deprecated and removed in a future version of Python,
|
||||||
|
but not before Python 3.13 reaches its end of life (see :pep:`749`).
|
||||||
|
When it is used, introspection tools like
|
||||||
|
:func:`annotationlib.get_annotations` and :func:`typing.get_type_hints` are
|
||||||
|
less likely to be able to resolve annotations at runtime.
|
||||||
|
|
||||||
|
|
||||||
.. rubric:: Footnotes
|
.. rubric:: Footnotes
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ and improvements in user-friendliness and correctness.
|
||||||
|
|
||||||
.. PEP-sized items next.
|
.. PEP-sized items next.
|
||||||
|
|
||||||
* :ref:`PEP 649: deferred evaluation of annotations <whatsnew314-pep649>`
|
* :ref:`PEP 649 and 749: deferred evaluation of annotations <whatsnew314-pep649>`
|
||||||
* :ref:`PEP 741: Python Configuration C API <whatsnew314-pep741>`
|
* :ref:`PEP 741: Python Configuration C API <whatsnew314-pep741>`
|
||||||
* :ref:`PEP 750: Template strings <whatsnew314-pep750>`
|
* :ref:`PEP 750: Template strings <whatsnew314-pep750>`
|
||||||
* :ref:`PEP 758: Allow except and except* expressions without parentheses <whatsnew314-pep758>`
|
* :ref:`PEP 758: Allow except and except* expressions without parentheses <whatsnew314-pep758>`
|
||||||
|
@ -362,18 +362,19 @@ Check :pep:`758` for more details.
|
||||||
|
|
||||||
.. _whatsnew314-pep649:
|
.. _whatsnew314-pep649:
|
||||||
|
|
||||||
PEP 649: deferred evaluation of annotations
|
PEP 649 and 749: deferred evaluation of annotations
|
||||||
-------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
The :term:`annotations <annotation>` on functions, classes, and modules are no
|
The :term:`annotations <annotation>` on functions, classes, and modules are no
|
||||||
longer evaluated eagerly. Instead, annotations are stored in special-purpose
|
longer evaluated eagerly. Instead, annotations are stored in special-purpose
|
||||||
:term:`annotate functions <annotate function>` and evaluated only when
|
:term:`annotate functions <annotate function>` and evaluated only when
|
||||||
necessary. This is specified in :pep:`649` and :pep:`749`.
|
necessary (except if ``from __future__ import annotations`` is used).
|
||||||
|
This is specified in :pep:`649` and :pep:`749`.
|
||||||
|
|
||||||
This change is designed to make annotations in Python more performant and more
|
This change is designed to make annotations in Python more performant and more
|
||||||
usable in most circumstances. The runtime cost for defining annotations is
|
usable in most circumstances. The runtime cost for defining annotations is
|
||||||
minimized, but it remains possible to introspect annotations at runtime.
|
minimized, but it remains possible to introspect annotations at runtime.
|
||||||
It is usually no longer necessary to enclose annotations in strings if they
|
It is no longer necessary to enclose annotations in strings if they
|
||||||
contain forward references.
|
contain forward references.
|
||||||
|
|
||||||
The new :mod:`annotationlib` module provides tools for inspecting deferred
|
The new :mod:`annotationlib` module provides tools for inspecting deferred
|
||||||
|
@ -409,7 +410,8 @@ writing annotations the same way you did with previous versions of Python.
|
||||||
You will likely be able to remove quoted strings in annotations, which are frequently
|
You will likely be able to remove quoted strings in annotations, which are frequently
|
||||||
used for forward references. Similarly, if you use ``from __future__ import annotations``
|
used for forward references. Similarly, if you use ``from __future__ import annotations``
|
||||||
to avoid having to write strings in annotations, you may well be able to
|
to avoid having to write strings in annotations, you may well be able to
|
||||||
remove that import. However, if you rely on third-party libraries that read annotations,
|
remove that import once you support only Python 3.14 and newer.
|
||||||
|
However, if you rely on third-party libraries that read annotations,
|
||||||
those libraries may need changes to support unquoted annotations before they
|
those libraries may need changes to support unquoted annotations before they
|
||||||
work as expected.
|
work as expected.
|
||||||
|
|
||||||
|
@ -422,6 +424,11 @@ annotations. For example, you may want to use :func:`annotationlib.get_annotatio
|
||||||
with the :attr:`~annotationlib.Format.FORWARDREF` format, as the :mod:`dataclasses`
|
with the :attr:`~annotationlib.Format.FORWARDREF` format, as the :mod:`dataclasses`
|
||||||
module now does.
|
module now does.
|
||||||
|
|
||||||
|
The external :pypi:`typing_extensions` package provides partial backports of some of the
|
||||||
|
functionality of the :mod:`annotationlib` module, such as the :class:`~annotationlib.Format`
|
||||||
|
enum and the :func:`~annotationlib.get_annotations` function. These can be used to
|
||||||
|
write cross-version code that takes advantage of the new behavior in Python 3.14.
|
||||||
|
|
||||||
Related changes
|
Related changes
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -433,6 +440,10 @@ functions in the standard library, there are many ways in which your code may
|
||||||
not work in Python 3.14. To safeguard your code against future changes,
|
not work in Python 3.14. To safeguard your code against future changes,
|
||||||
use only the documented functionality of the :mod:`annotationlib` module.
|
use only the documented functionality of the :mod:`annotationlib` module.
|
||||||
|
|
||||||
|
In particular, do not read annotations directly from the namespace dictionary
|
||||||
|
attribute of type objects. Use :func:`annotationlib.get_annotate_from_class_namespace`
|
||||||
|
during class construction and :func:`annotationlib.get_annotations` afterwards.
|
||||||
|
|
||||||
``from __future__ import annotations``
|
``from __future__ import annotations``
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -444,8 +455,10 @@ Python without deferred evaluation of annotations, reaches its end of life in 20
|
||||||
In Python 3.14, the behavior of code using ``from __future__ import annotations``
|
In Python 3.14, the behavior of code using ``from __future__ import annotations``
|
||||||
is unchanged.
|
is unchanged.
|
||||||
|
|
||||||
|
(Contributed by Jelle Zijlstra in :gh:`119180`; :pep:`649` was written by Larry Hastings.)
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
:pep:`649`.
|
:pep:`649` and :pep:`749`.
|
||||||
|
|
||||||
|
|
||||||
Improved error messages
|
Improved error messages
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue