mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00
605 lines
26 KiB
ReStructuredText
605 lines
26 KiB
ReStructuredText
:mod:`!annotationlib` --- Functionality for introspecting annotations
|
|
=====================================================================
|
|
|
|
.. module:: annotationlib
|
|
:synopsis: Functionality for introspecting annotations
|
|
|
|
|
|
**Source code:** :source:`Lib/annotationlib.py`
|
|
|
|
.. testsetup:: default
|
|
|
|
import annotationlib
|
|
from annotationlib import *
|
|
|
|
--------------
|
|
|
|
The :mod:`!annotationlib` module provides tools for introspecting
|
|
:term:`annotations <annotation>` on modules, classes, and functions.
|
|
|
|
Annotations are :ref:`lazily evaluated <lazy-evaluation>` and often contain
|
|
forward references to objects that are not yet defined when the annotation
|
|
is created. This module provides a set of low-level tools that can be used to retrieve annotations in a reliable way, even
|
|
in the presence of forward references and other edge cases.
|
|
|
|
This module supports retrieving annotations in three main formats
|
|
(see :class:`Format`), each of which works best for different use cases:
|
|
|
|
* :attr:`~Format.VALUE` evaluates the annotations and returns their value.
|
|
This is most straightforward to work with, but it may raise errors,
|
|
for example if the annotations contain references to undefined names.
|
|
* :attr:`~Format.FORWARDREF` returns :class:`ForwardRef` objects
|
|
for annotations that cannot be resolved, allowing you to inspect the
|
|
annotations without evaluating them. This is useful when you need to
|
|
work with annotations that may contain unresolved forward references.
|
|
* :attr:`~Format.STRING` returns the annotations as a string, similar
|
|
to how it would appear in the source file. This is useful for documentation
|
|
generators that want to display annotations in a readable way.
|
|
|
|
The :func:`get_annotations` function is the main entry point for
|
|
retrieving annotations. Given a function, class, or module, it returns
|
|
an annotations dictionary in the requested format. This module also provides
|
|
functionality for working directly with the :term:`annotate function`
|
|
that is used to evaluate annotations, such as :func:`get_annotate_from_class_namespace`
|
|
and :func:`call_annotate_function`, as well as the
|
|
:func:`call_evaluate_function` function for working with
|
|
:term:`evaluate functions <evaluate function>`.
|
|
|
|
|
|
.. seealso::
|
|
|
|
:pep:`649` proposed the current model for how annotations work in Python.
|
|
|
|
:pep:`749` expanded on various aspects of :pep:`649` and introduced the
|
|
:mod:`!annotationlib` module.
|
|
|
|
:ref:`annotations-howto` provides best practices for working with
|
|
annotations.
|
|
|
|
:pypi:`typing-extensions` provides a backport of :func:`get_annotations`
|
|
that works on earlier versions of Python.
|
|
|
|
Annotation semantics
|
|
--------------------
|
|
|
|
The way annotations are evaluated has changed over the history of Python 3,
|
|
and currently still depends on a :ref:`future import <future>`.
|
|
There have been execution models for annotations:
|
|
|
|
* *Stock semantics* (default in Python 3.0 through 3.13; see :pep:`3107`
|
|
and :pep:`526`): Annotations are evaluated eagerly, as they are
|
|
encountered in the source code.
|
|
* *Stringified annotations* (used with ``from __future__ import annotations``
|
|
in Python 3.7 and newer; see :pep:`563`): Annotations are stored as
|
|
strings only.
|
|
* *Deferred evaluation* (default in Python 3.14 and newer; see :pep:`649` and
|
|
:pep:`749`): Annotations are evaluated lazily, only when they are accessed.
|
|
|
|
As an example, consider the following program::
|
|
|
|
def func(a: Cls) -> None:
|
|
print(a)
|
|
|
|
class Cls: pass
|
|
|
|
print(func.__annotations__)
|
|
|
|
This will behave as follows:
|
|
|
|
* Under stock semantics (Python 3.13 and earlier), it will throw a
|
|
:exc:`NameError` at the line where ``func`` is defined,
|
|
because ``Cls`` is an undefined name at that point.
|
|
* Under stringified annotations (if ``from __future__ import annotations``
|
|
is used), it will print ``{'a': 'Cls', 'return': 'None'}``.
|
|
* Under deferred evaluation (Python 3.14 and later), it will print
|
|
``{'a': <class 'Cls'>, 'return': None}``.
|
|
|
|
Stock semantics were used when function annotations were first introduced
|
|
in Python 3.0 (by :pep:`3107`) because this was the simplest, most obvious
|
|
way to implement annotations. The same execution model was used when variable
|
|
annotations were introduced in Python 3.6 (by :pep:`526`). However,
|
|
stock semantics caused problems when using annotations as type hints,
|
|
such as a need to refer to names that are not yet defined when the
|
|
annotation is encountered. In addition, there were performance problems
|
|
with executing annotations at module import time. Therefore, in Python 3.7,
|
|
:pep:`563` introduced the ability to store annotations as strings using the
|
|
``from __future__ import annotations`` syntax. The plan at the time was to
|
|
eventually make this behavior the default, but a problem appeared:
|
|
stringified annotations are more difficult to process for those who
|
|
introspect annotations at runtime. An alternative proposal, :pep:`649`,
|
|
introduced the third execution model, deferred evaluation, and was implemented
|
|
in Python 3.14. Stringified annotations are still used if
|
|
``from __future__ import annotations`` is present, but this behavior will
|
|
eventually be removed.
|
|
|
|
Classes
|
|
-------
|
|
|
|
.. class:: Format
|
|
|
|
An :class:`~enum.IntEnum` describing the formats in which annotations
|
|
can be returned. Members of the enum, or their equivalent integer values,
|
|
can be passed to :func:`get_annotations` and other functions in this
|
|
module, as well as to :attr:`~object.__annotate__` functions.
|
|
|
|
.. attribute:: VALUE
|
|
:value: 1
|
|
|
|
Values are the result of evaluating the annotation expressions.
|
|
|
|
.. 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: 4
|
|
|
|
Values are the text string of the annotation as it appears in the
|
|
source code, up to modifications including, but not restricted to,
|
|
whitespace normalizations and constant values optimizations.
|
|
|
|
The exact values of these strings may change in future versions of Python.
|
|
|
|
.. versionadded:: 3.14
|
|
|
|
.. class:: ForwardRef
|
|
|
|
A proxy object for forward references in annotations.
|
|
|
|
Instances of this class are returned when the :attr:`~Format.FORWARDREF`
|
|
format is used and annotations contain a name that cannot be resolved.
|
|
This can happen when a forward reference is used in an annotation, such as
|
|
when a class is referenced before it is defined.
|
|
|
|
.. attribute:: __forward_arg__
|
|
|
|
A string containing the code that was evaluated to produce the
|
|
:class:`~ForwardRef`. The string may not be exactly equivalent
|
|
to the original source.
|
|
|
|
.. method:: evaluate(*, owner=None, globals=None, locals=None, type_params=None, format=Format.VALUE)
|
|
|
|
Evaluate the forward reference, returning its value.
|
|
|
|
If the *format* argument is :attr:`~Format.VALUE` (the default),
|
|
this method may throw an exception, such as :exc:`NameError`, if the forward
|
|
reference refers to a name that cannot be resolved. The arguments to this
|
|
method can be used to provide bindings for names that would otherwise
|
|
be undefined. If the *format* argument is :attr:`~Format.FORWARDREF`,
|
|
the method will never throw an exception, but may return a :class:`~ForwardRef`
|
|
instance. For example, if the forward reference object contains the code
|
|
``list[undefined]``, where ``undefined`` is a name that is not defined,
|
|
evaluating it with the :attr:`~Format.FORWARDREF` format will return
|
|
``list[ForwardRef('undefined')]``. If the *format* argument is
|
|
:attr:`~Format.STRING`, the method will return :attr:`~ForwardRef.__forward_arg__`.
|
|
|
|
The *owner* parameter provides the preferred mechanism for passing scope
|
|
information to this method. The owner of a :class:`~ForwardRef` is the
|
|
object that contains the annotation from which the :class:`~ForwardRef`
|
|
derives, such as a module object, type object, or function object.
|
|
|
|
The *globals*, *locals*, and *type_params* parameters provide a more precise
|
|
mechanism for influencing the names that are available when the :class:`~ForwardRef`
|
|
is evaluated. *globals* and *locals* are passed to :func:`eval`, representing
|
|
the global and local namespaces in which the name is evaluated.
|
|
The *type_params* parameter is relevant for objects created using the native
|
|
syntax for :ref:`generic classes <generic-classes>` and :ref:`functions <generic-functions>`.
|
|
It is a tuple of :ref:`type parameters <type-params>` that are in scope
|
|
while the forward reference is being evaluated. For example, if evaluating a
|
|
:class:`~ForwardRef` retrieved from an annotation found in the class namespace
|
|
of a generic class ``C``, *type_params* should be set to ``C.__type_params__``.
|
|
|
|
:class:`~ForwardRef` instances returned by :func:`get_annotations`
|
|
retain references to information about the scope they originated from,
|
|
so calling this method with no further arguments may be sufficient to
|
|
evaluate such objects. :class:`~ForwardRef` instances created by other
|
|
means may not have any information about their scope, so passing
|
|
arguments to this method may be necessary to evaluate them successfully.
|
|
|
|
If no *owner*, *globals*, *locals*, or *type_params* are provided and the
|
|
:class:`~ForwardRef` does not contain information about its origin,
|
|
empty globals and locals dictionaries are used.
|
|
|
|
.. versionadded:: 3.14
|
|
|
|
|
|
Functions
|
|
---------
|
|
|
|
.. function:: annotations_to_string(annotations)
|
|
|
|
Convert an annotations dict containing runtime values to a
|
|
dict containing only strings. If the values are not already strings,
|
|
they are converted using :func:`type_repr`.
|
|
This is meant as a helper for user-provided
|
|
annotate functions that support the :attr:`~Format.STRING` format but
|
|
do not have access to the code creating the annotations.
|
|
|
|
For example, this is used to implement the :attr:`~Format.STRING` for
|
|
:class:`typing.TypedDict` classes created through the functional syntax:
|
|
|
|
.. doctest::
|
|
|
|
>>> from typing import TypedDict
|
|
>>> Movie = TypedDict("movie", {"name": str, "year": int})
|
|
>>> get_annotations(Movie, format=Format.STRING)
|
|
{'name': 'str', 'year': 'int'}
|
|
|
|
.. versionadded:: 3.14
|
|
|
|
.. function:: call_annotate_function(annotate, format, *, owner=None)
|
|
|
|
Call the :term:`annotate function` *annotate* with the given *format*,
|
|
a member of the :class:`Format` enum, and return the annotations
|
|
dictionary produced by the function.
|
|
|
|
This helper function is required because annotate functions generated by
|
|
the compiler for functions, classes, and modules only support
|
|
the :attr:`~Format.VALUE` format when called directly.
|
|
To support other formats, this function calls the annotate function
|
|
in a special environment that allows it to produce annotations in the
|
|
other formats. This is a useful building block when implementing
|
|
functionality that needs to partially evaluate annotations while a class
|
|
is being constructed.
|
|
|
|
*owner* is the object that owns the annotation function, usually
|
|
a function, class, or module. If provided, it is used in the
|
|
:attr:`~Format.FORWARDREF` format to produce a :class:`ForwardRef`
|
|
object that carries more information.
|
|
|
|
.. seealso::
|
|
|
|
:PEP:`PEP 649 <649#the-stringizer-and-the-fake-globals-environment>`
|
|
contains an explanation of the implementation technique used by this
|
|
function.
|
|
|
|
.. versionadded:: 3.14
|
|
|
|
.. function:: call_evaluate_function(evaluate, format, *, owner=None)
|
|
|
|
Call the :term:`evaluate function` *evaluate* with the given *format*,
|
|
a member of the :class:`Format` enum, and return the value produced by
|
|
the function. This is similar to :func:`call_annotate_function`,
|
|
but the latter always returns a dictionary mapping strings to annotations,
|
|
while this function returns a single value.
|
|
|
|
This is intended for use with the evaluate functions generated for lazily
|
|
evaluated elements related to type aliases and type parameters:
|
|
|
|
* :meth:`typing.TypeAliasType.evaluate_value`, the value of type aliases
|
|
* :meth:`typing.TypeVar.evaluate_bound`, the bound of type variables
|
|
* :meth:`typing.TypeVar.evaluate_constraints`, the constraints of
|
|
type variables
|
|
* :meth:`typing.TypeVar.evaluate_default`, the default value of
|
|
type variables
|
|
* :meth:`typing.ParamSpec.evaluate_default`, the default value of
|
|
parameter specifications
|
|
* :meth:`typing.TypeVarTuple.evaluate_default`, the default value of
|
|
type variable tuples
|
|
|
|
*owner* is the object that owns the evaluate function, such as the type
|
|
alias or type variable object.
|
|
|
|
*format* can be used to control the format in which the value is returned:
|
|
|
|
.. doctest::
|
|
|
|
>>> type Alias = undefined
|
|
>>> call_evaluate_function(Alias.evaluate_value, Format.VALUE)
|
|
Traceback (most recent call last):
|
|
...
|
|
NameError: name 'undefined' is not defined
|
|
>>> call_evaluate_function(Alias.evaluate_value, Format.FORWARDREF)
|
|
ForwardRef('undefined')
|
|
>>> call_evaluate_function(Alias.evaluate_value, Format.STRING)
|
|
'undefined'
|
|
|
|
.. versionadded:: 3.14
|
|
|
|
.. function:: get_annotate_from_class_namespace(namespace)
|
|
|
|
Retrieve the :term:`annotate function` from a class namespace dictionary *namespace*.
|
|
Return :const:`!None` if the namespace does not contain an annotate function.
|
|
This is primarily useful before the class has been fully created (e.g., in a metaclass);
|
|
after the class exists, the annotate function can be retrieved with ``cls.__annotate__``.
|
|
See :ref:`below <annotationlib-metaclass>` for an example using this function in a metaclass.
|
|
|
|
.. versionadded:: 3.14
|
|
|
|
.. function:: get_annotations(obj, *, globals=None, locals=None, eval_str=False, format=Format.VALUE)
|
|
|
|
Compute the annotations dict for an object.
|
|
|
|
*obj* may be a callable, class, module, or other object with
|
|
:attr:`~object.__annotate__` or :attr:`~object.__annotations__` attributes.
|
|
Passing any other object raises :exc:`TypeError`.
|
|
|
|
The *format* parameter controls the format in which annotations are returned,
|
|
and must be a member of the :class:`Format` enum or its integer equivalent.
|
|
The different formats work as follows:
|
|
|
|
* VALUE: :attr:`!object.__annotations__` is tried first; if that does not exist,
|
|
the :attr:`!object.__annotate__` function is called if it exists.
|
|
* FORWARDREF: If :attr:`!object.__annotations__` exists and can be evaluated successfully,
|
|
it is used; otherwise, the :attr:`!object.__annotate__` function is called. If it
|
|
does not exist either, :attr:`!object.__annotations__` is tried again and any error
|
|
from accessing it is re-raised.
|
|
* STRING: If :attr:`!object.__annotate__` exists, it is called first;
|
|
otherwise, :attr:`!object.__annotations__` is used and stringified
|
|
using :func:`annotations_to_string`.
|
|
|
|
Returns a dict. :func:`!get_annotations` returns a new dict every time
|
|
it's called; calling it twice on the same object will return two
|
|
different but equivalent dicts.
|
|
|
|
This function handles several details for you:
|
|
|
|
* If *eval_str* is true, values of type :class:`!str` will
|
|
be un-stringized using :func:`eval`. This is intended
|
|
for use with stringized annotations
|
|
(``from __future__ import annotations``). It is an error
|
|
to set *eval_str* to true with formats other than :attr:`Format.VALUE`.
|
|
* If *obj* doesn't have an annotations dict, returns an
|
|
empty dict. (Functions and methods always have an
|
|
annotations dict; classes, modules, and other types of
|
|
callables may not.)
|
|
* Ignores inherited annotations on classes, as well as annotations
|
|
on metaclasses. If a class
|
|
doesn't have its own annotations dict, returns an empty dict.
|
|
* All accesses to object members and dict values are done
|
|
using ``getattr()`` and ``dict.get()`` for safety.
|
|
|
|
*eval_str* controls whether or not values of type :class:`!str` are
|
|
replaced with the result of calling :func:`eval` on those values:
|
|
|
|
* If eval_str is true, :func:`eval` is called on values of type
|
|
:class:`!str`. (Note that :func:`!get_annotations` doesn't catch
|
|
exceptions; if :func:`eval` raises an exception, it will unwind
|
|
the stack past the :func:`!get_annotations` call.)
|
|
* If *eval_str* is false (the default), values of type :class:`!str` are
|
|
unchanged.
|
|
|
|
*globals* and *locals* are passed in to :func:`eval`; see the documentation
|
|
for :func:`eval` for more information. If *globals* or *locals*
|
|
is :const:`!None`, this function may replace that value with a
|
|
context-specific default, contingent on ``type(obj)``:
|
|
|
|
* If *obj* is a module, *globals* defaults to ``obj.__dict__``.
|
|
* If *obj* is a class, *globals* defaults to
|
|
``sys.modules[obj.__module__].__dict__`` and *locals* defaults
|
|
to the *obj* class namespace.
|
|
* If *obj* is a callable, *globals* defaults to
|
|
:attr:`obj.__globals__ <function.__globals__>`,
|
|
although if *obj* is a wrapped function (using
|
|
:func:`functools.update_wrapper`) or a :class:`functools.partial` object,
|
|
it is unwrapped until a non-wrapped function is found.
|
|
|
|
Calling :func:`!get_annotations` is best practice for accessing the
|
|
annotations dict of any object. See :ref:`annotations-howto` for
|
|
more information on annotations best practices.
|
|
|
|
.. doctest::
|
|
|
|
>>> def f(a: int, b: str) -> float:
|
|
... pass
|
|
>>> get_annotations(f)
|
|
{'a': <class 'int'>, 'b': <class 'str'>, 'return': <class 'float'>}
|
|
|
|
.. versionadded:: 3.14
|
|
|
|
.. function:: type_repr(value)
|
|
|
|
Convert an arbitrary Python value to a format suitable for use by the
|
|
:attr:`~Format.STRING` format. This calls :func:`repr` for most
|
|
objects, but has special handling for some objects, such as type objects.
|
|
|
|
This is meant as a helper for user-provided
|
|
annotate functions that support the :attr:`~Format.STRING` format but
|
|
do not have access to the code creating the annotations. It can also
|
|
be used to provide a user-friendly string representation for other
|
|
objects that contain values that are commonly encountered in annotations.
|
|
|
|
.. versionadded:: 3.14
|
|
|
|
|
|
Recipes
|
|
-------
|
|
|
|
.. _annotationlib-metaclass:
|
|
|
|
Using annotations in a metaclass
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
A :ref:`metaclass <metaclasses>` may want to inspect or even modify the annotations
|
|
in a class body during class creation. Doing so requires retrieving annotations
|
|
from the class namespace dictionary. For classes created with
|
|
``from __future__ import annotations``, the annotations will be in the ``__annotations__``
|
|
key of the dictionary. For other classes with annotations,
|
|
:func:`get_annotate_from_class_namespace` can be used to get the
|
|
annotate function, and :func:`call_annotate_function` can be used to call it and
|
|
retrieve the annotations. Using the :attr:`~Format.FORWARDREF` format will usually
|
|
be best, because this allows the annotations to refer to names that cannot yet be
|
|
resolved when the class is created.
|
|
|
|
To modify the annotations, it is best to create a wrapper annotate function
|
|
that calls the original annotate function, makes any necessary adjustments, and
|
|
returns the result.
|
|
|
|
Below is an example of a metaclass that filters out all :class:`typing.ClassVar`
|
|
annotations from the class and puts them in a separate attribute:
|
|
|
|
.. code-block:: python
|
|
|
|
import annotationlib
|
|
import typing
|
|
|
|
class ClassVarSeparator(type):
|
|
def __new__(mcls, name, bases, ns):
|
|
if "__annotations__" in ns: # from __future__ import annotations
|
|
annotations = ns["__annotations__"]
|
|
classvar_keys = {
|
|
key for key, value in annotations.items()
|
|
# Use string comparison for simplicity; a more robust solution
|
|
# could use annotationlib.ForwardRef.evaluate
|
|
if value.startswith("ClassVar")
|
|
}
|
|
classvars = {key: annotations[key] for key in classvar_keys}
|
|
ns["__annotations__"] = {
|
|
key: value for key, value in annotations.items()
|
|
if key not in classvar_keys
|
|
}
|
|
wrapped_annotate = None
|
|
elif annotate := annotationlib.get_annotate_from_class_namespace(ns):
|
|
annotations = annotationlib.call_annotate_function(
|
|
annotate, format=annotationlib.Format.FORWARDREF
|
|
)
|
|
classvar_keys = {
|
|
key for key, value in annotations.items()
|
|
if typing.get_origin(value) is typing.ClassVar
|
|
}
|
|
classvars = {key: annotations[key] for key in classvar_keys}
|
|
|
|
def wrapped_annotate(format):
|
|
annos = annotationlib.call_annotate_function(annotate, format, owner=typ)
|
|
return {key: value for key, value in annos.items() if key not in classvar_keys}
|
|
|
|
else: # no annotations
|
|
classvars = {}
|
|
wrapped_annotate = None
|
|
typ = super().__new__(mcls, name, bases, ns)
|
|
|
|
if wrapped_annotate is not None:
|
|
# Wrap the original __annotate__ with a wrapper that removes ClassVars
|
|
typ.__annotate__ = wrapped_annotate
|
|
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'}
|