Skip to content

Commit 3396df5

Browse files
gh-119180: More documentation for PEP 649/749 (#133552)
The SC asked that the Appendix in PEP-749 be added to the docs. Co-authored-by: Adam Turner <[email protected]>
1 parent 1d3eace commit 3396df5

File tree

3 files changed

+141
-8
lines changed

3 files changed

+141
-8
lines changed

Doc/library/annotationlib.rst

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,3 +485,117 @@ annotations from the class and puts them in a separate attribute:
485485
typ.classvars = classvars # Store the ClassVars in a separate attribute
486486
return typ
487487
488+
489+
Limitations of the ``STRING`` format
490+
------------------------------------
491+
492+
The :attr:`~Format.STRING` format is meant to approximate the source code
493+
of the annotation, but the implementation strategy used means that it is not
494+
always possible to recover the exact source code.
495+
496+
First, the stringifier of course cannot recover any information that is not present in
497+
the compiled code, including comments, whitespace, parenthesization, and operations that
498+
get simplified by the compiler.
499+
500+
Second, the stringifier can intercept almost all operations that involve names looked
501+
up in some scope, but it cannot intercept operations that operate fully on constants.
502+
As a corollary, this also means it is not safe to request the ``STRING`` format on
503+
untrusted code: Python is powerful enough that it is possible to achieve arbitrary
504+
code execution even with no access to any globals or builtins. For example:
505+
506+
.. code-block:: pycon
507+
508+
>>> def f(x: (1).__class__.__base__.__subclasses__()[-1].__init__.__builtins__["print"]("Hello world")): pass
509+
...
510+
>>> annotationlib.get_annotations(f, format=annotationlib.Format.SOURCE)
511+
Hello world
512+
{'x': 'None'}
513+
514+
.. note::
515+
This particular example works as of the time of writing, but it relies on
516+
implementation details and is not guaranteed to work in the future.
517+
518+
Among the different kinds of expressions that exist in Python,
519+
as represented by the :mod:`ast` module, some expressions are supported,
520+
meaning that the ``STRING`` format can generally recover the original source code;
521+
others are unsupported, meaning that they may result in incorrect output or an error.
522+
523+
The following are supported (sometimes with caveats):
524+
525+
* :class:`ast.BinOp`
526+
* :class:`ast.UnaryOp`
527+
528+
* :class:`ast.Invert` (``~``), :class:`ast.UAdd` (``+``), and :class:`ast.USub` (``-``) are supported
529+
* :class:`ast.Not` (``not``) is not supported
530+
531+
* :class:`ast.Dict` (except when using ``**`` unpacking)
532+
* :class:`ast.Set`
533+
* :class:`ast.Compare`
534+
535+
* :class:`ast.Eq` and :class:`ast.NotEq` are supported
536+
* :class:`ast.Lt`, :class:`ast.LtE`, :class:`ast.Gt`, and :class:`ast.GtE` are supported, but the operand may be flipped
537+
* :class:`ast.Is`, :class:`ast.IsNot`, :class:`ast.In`, and :class:`ast.NotIn` are not supported
538+
539+
* :class:`ast.Call` (except when using ``**`` unpacking)
540+
* :class:`ast.Constant` (though not the exact representation of the constant; for example, escape
541+
sequences in strings are lost; hexadecimal numbers are converted to decimal)
542+
* :class:`ast.Attribute` (assuming the value is not a constant)
543+
* :class:`ast.Subscript` (assuming the value is not a constant)
544+
* :class:`ast.Starred` (``*`` unpacking)
545+
* :class:`ast.Name`
546+
* :class:`ast.List`
547+
* :class:`ast.Tuple`
548+
* :class:`ast.Slice`
549+
550+
The following are unsupported, but throw an informative error when encountered by the
551+
stringifier:
552+
553+
* :class:`ast.FormattedValue` (f-strings; error is not detected if conversion specifiers like ``!r``
554+
are used)
555+
* :class:`ast.JoinedStr` (f-strings)
556+
557+
The following are unsupported and result in incorrect output:
558+
559+
* :class:`ast.BoolOp` (``and`` and ``or``)
560+
* :class:`ast.IfExp`
561+
* :class:`ast.Lambda`
562+
* :class:`ast.ListComp`
563+
* :class:`ast.SetComp`
564+
* :class:`ast.DictComp`
565+
* :class:`ast.GeneratorExp`
566+
567+
The following are disallowed in annotation scopes and therefore not relevant:
568+
569+
* :class:`ast.NamedExpr` (``:=``)
570+
* :class:`ast.Await`
571+
* :class:`ast.Yield`
572+
* :class:`ast.YieldFrom`
573+
574+
575+
Limitations of the ``FORWARDREF`` format
576+
----------------------------------------
577+
578+
The :attr:`~Format.FORWARDREF` format aims to produce real values as much
579+
as possible, with anything that cannot be resolved replaced with
580+
:class:`ForwardRef` objects. It is affected by broadly the same Limitations
581+
as the :attr:`~Format.STRING` format: annotations that perform operations on
582+
literals or that use unsupported expression types may raise exceptions when
583+
evaluated using the :attr:`~Format.FORWARDREF` format.
584+
585+
Below are a few examples of the behavior with unsupported expressions:
586+
587+
.. code-block:: pycon
588+
589+
>>> from annotationlib import get_annotations, Format
590+
>>> def zerodiv(x: 1 / 0): ...
591+
>>> get_annotations(zerodiv, format=Format.STRING)
592+
Traceback (most recent call last):
593+
...
594+
ZeroDivisionError: division by zero
595+
>>> get_annotations(zerodiv, format=Format.FORWARDREF)
596+
Traceback (most recent call last):
597+
...
598+
ZeroDivisionError: division by zero
599+
>>> def ifexp(x: 1 if y else 0): ...
600+
>>> get_annotations(ifexp, format=Format.STRING)
601+
{'x': '1'}

Doc/reference/compound_stmts.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1885,7 +1885,7 @@ expressions. The presence of annotations does not change the runtime semantics o
18851885
the code, except if some mechanism is used that introspects and uses the annotations
18861886
(such as :mod:`dataclasses` or :func:`functools.singledispatch`).
18871887

1888-
By default, annotations are lazily evaluated in a :ref:`annotation scope <annotation-scopes>`.
1888+
By default, annotations are lazily evaluated in an :ref:`annotation scope <annotation-scopes>`.
18891889
This means that they are not evaluated when the code containing the annotation is evaluated.
18901890
Instead, the interpreter saves information that can be used to evaluate the annotation later
18911891
if requested. The :mod:`annotationlib` module provides tools for evaluating annotations.
@@ -1898,6 +1898,12 @@ all annotations are instead stored as strings::
18981898
>>> f.__annotations__
18991899
{'param': 'annotation'}
19001900

1901+
This future statement will be deprecated and removed in a future version of Python,
1902+
but not before Python 3.13 reaches its end of life (see :pep:`749`).
1903+
When it is used, introspection tools like
1904+
:func:`annotationlib.get_annotations` and :func:`typing.get_type_hints` are
1905+
less likely to be able to resolve annotations at runtime.
1906+
19011907

19021908
.. rubric:: Footnotes
19031909

Doc/whatsnew/3.14.rst

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ and improvements in user-friendliness and correctness.
8282

8383
.. PEP-sized items next.
8484
85-
* :ref:`PEP 649: deferred evaluation of annotations <whatsnew314-pep649>`
85+
* :ref:`PEP 649 and 749: deferred evaluation of annotations <whatsnew314-pep649>`
8686
* :ref:`PEP 741: Python Configuration C API <whatsnew314-pep741>`
8787
* :ref:`PEP 750: Template strings <whatsnew314-pep750>`
8888
* :ref:`PEP 758: Allow except and except* expressions without parentheses <whatsnew314-pep758>`
@@ -362,18 +362,19 @@ Check :pep:`758` for more details.
362362

363363
.. _whatsnew314-pep649:
364364

365-
PEP 649: deferred evaluation of annotations
366-
-------------------------------------------
365+
PEP 649 and 749: deferred evaluation of annotations
366+
---------------------------------------------------
367367

368368
The :term:`annotations <annotation>` on functions, classes, and modules are no
369369
longer evaluated eagerly. Instead, annotations are stored in special-purpose
370370
:term:`annotate functions <annotate function>` and evaluated only when
371-
necessary. This is specified in :pep:`649` and :pep:`749`.
371+
necessary (except if ``from __future__ import annotations`` is used).
372+
This is specified in :pep:`649` and :pep:`749`.
372373

373374
This change is designed to make annotations in Python more performant and more
374375
usable in most circumstances. The runtime cost for defining annotations is
375376
minimized, but it remains possible to introspect annotations at runtime.
376-
It is usually no longer necessary to enclose annotations in strings if they
377+
It is no longer necessary to enclose annotations in strings if they
377378
contain forward references.
378379

379380
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.
409410
You will likely be able to remove quoted strings in annotations, which are frequently
410411
used for forward references. Similarly, if you use ``from __future__ import annotations``
411412
to avoid having to write strings in annotations, you may well be able to
412-
remove that import. However, if you rely on third-party libraries that read annotations,
413+
remove that import once you support only Python 3.14 and newer.
414+
However, if you rely on third-party libraries that read annotations,
413415
those libraries may need changes to support unquoted annotations before they
414416
work as expected.
415417

@@ -422,6 +424,11 @@ annotations. For example, you may want to use :func:`annotationlib.get_annotatio
422424
with the :attr:`~annotationlib.Format.FORWARDREF` format, as the :mod:`dataclasses`
423425
module now does.
424426

427+
The external :pypi:`typing_extensions` package provides partial backports of some of the
428+
functionality of the :mod:`annotationlib` module, such as the :class:`~annotationlib.Format`
429+
enum and the :func:`~annotationlib.get_annotations` function. These can be used to
430+
write cross-version code that takes advantage of the new behavior in Python 3.14.
431+
425432
Related changes
426433
^^^^^^^^^^^^^^^
427434

@@ -433,6 +440,10 @@ functions in the standard library, there are many ways in which your code may
433440
not work in Python 3.14. To safeguard your code against future changes,
434441
use only the documented functionality of the :mod:`annotationlib` module.
435442

443+
In particular, do not read annotations directly from the namespace dictionary
444+
attribute of type objects. Use :func:`annotationlib.get_annotate_from_class_namespace`
445+
during class construction and :func:`annotationlib.get_annotations` afterwards.
446+
436447
``from __future__ import annotations``
437448
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
438449

@@ -444,8 +455,10 @@ Python without deferred evaluation of annotations, reaches its end of life in 20
444455
In Python 3.14, the behavior of code using ``from __future__ import annotations``
445456
is unchanged.
446457

458+
(Contributed by Jelle Zijlstra in :gh:`119180`; :pep:`649` was written by Larry Hastings.)
459+
447460
.. seealso::
448-
:pep:`649`.
461+
:pep:`649` and :pep:`749`.
449462

450463

451464
Improved error messages

0 commit comments

Comments
 (0)