mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +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
|
||||
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'}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue