aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Doc/library/annotationlib.rst
diff options
context:
space:
mode:
Diffstat (limited to 'Doc/library/annotationlib.rst')
-rw-r--r--Doc/library/annotationlib.rst140
1 files changed, 127 insertions, 13 deletions
diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst
index ff9578b6088..41c9ce479ff 100644
--- a/Doc/library/annotationlib.rst
+++ b/Doc/library/annotationlib.rst
@@ -127,16 +127,27 @@ Classes
Values are the result of evaluating the annotation expressions.
- .. attribute:: FORWARDREF
+ .. attribute:: VALUE_WITH_FAKE_GLOBALS
:value: 2
+ Special value used to signal that an annotate function is being
+ evaluated in a special environment with fake globals. When passed this
+ value, annotate functions should either return the same value as for
+ the :attr:`Format.VALUE` format, or raise :exc:`NotImplementedError`
+ to signal that they do not support execution in this environment.
+ This format is only used internally and should not be passed to
+ the functions in this module.
+
+ .. attribute:: FORWARDREF
+ :value: 3
+
Values are real annotation values (as per :attr:`Format.VALUE` format)
for defined values, and :class:`ForwardRef` proxies for undefined
values. Real objects may contain references to :class:`ForwardRef`
proxy objects.
.. attribute:: STRING
- :value: 3
+ :value: 4
Values are the text string of the annotation as it appears in the
source code, up to modifications including, but not restricted to,
@@ -144,17 +155,6 @@ Classes
The exact values of these strings may change in future versions of Python.
- .. attribute:: VALUE_WITH_FAKE_GLOBALS
- :value: 4
-
- Special value used to signal that an annotate function is being
- evaluated in a special environment with fake globals. When passed this
- value, annotate functions should either return the same value as for
- the :attr:`Format.VALUE` format, or raise :exc:`NotImplementedError`
- to signal that they do not support execution in this environment.
- This format is only used internally and should not be passed to
- the functions in this module.
-
.. versionadded:: 3.14
.. class:: ForwardRef
@@ -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'}