[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:
Miss Islington (bot) 2025-05-11 17:49:06 +02:00 committed by GitHub
parent 507715d5f7
commit a3475e68bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 141 additions and 8 deletions

View file

@ -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
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'}