From 20dcb6280a46714c6f9ed3a922adc8b98c6ad865 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 23 Jul 2024 21:21:53 -0700 Subject: [PATCH 01/20] gh-119180: Documentation for PEP 649/749 --- Doc/glossary.rst | 14 +++-- Doc/library/__future__.rst | 14 ++--- Doc/library/python.rst | 1 + Doc/reference/compound_stmts.rst | 44 ++++++++++--- Doc/reference/datamodel.rst | 104 +++++++++++++++++++++++++++++-- Doc/reference/executionmodel.rst | 23 ++++--- Doc/reference/simple_stmts.rst | 26 ++++---- 7 files changed, 181 insertions(+), 45 deletions(-) diff --git a/Doc/glossary.rst b/Doc/glossary.rst index 281dde30dc78ed..19e2b5615a4c49 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -36,6 +36,11 @@ Glossary and loaders (in the :mod:`importlib.abc` module). You can create your own ABCs with the :mod:`abc` module. + annotate function + A function that can be called to retrieve the :term:`annotations ` + of an object. This function is accessible as the :attr:`~object.__annotate__` + attribute of functions, classes, and modules. + annotation A label associated with a variable, a class attribute or a function parameter or return value, @@ -43,12 +48,11 @@ Glossary Annotations of local variables cannot be accessed at runtime, but annotations of global variables, class attributes, and functions - are stored in the :attr:`__annotations__` - special attribute of modules, classes, and functions, - respectively. + can be retrieved by calling :func:`annotationlib.get_annotations` + on modules, classes, and functions, respectively. - See :term:`variable annotation`, :term:`function annotation`, :pep:`484` - and :pep:`526`, which describe this functionality. + See :term:`variable annotation`, :term:`function annotation`, :pep:`484`, + :pep:`526` and :pep:`649`, which describe this functionality. Also see :ref:`annotations-howto` for best practices on working with annotations. diff --git a/Doc/library/__future__.rst b/Doc/library/__future__.rst index 1ebff4409b1e95..6a1179434acd5a 100644 --- a/Doc/library/__future__.rst +++ b/Doc/library/__future__.rst @@ -64,8 +64,10 @@ language using this mechanism: | generator_stop | 3.5.0b1 | 3.7 | :pep:`479`: | | | | | *StopIteration handling inside generators* | +------------------+-------------+--------------+---------------------------------------------+ -| annotations | 3.7.0b1 | TBD [1]_ | :pep:`563`: | -| | | | *Postponed evaluation of annotations* | +| annotations | 3.7.0b1 | Never [1]_ | :pep:`563`: | +| | | | *Postponed evaluation of annotations*, | +| | | | :pep:`649`: *Deferred evalutation of | +| | | | annotations using descriptors* | +------------------+-------------+--------------+---------------------------------------------+ .. XXX Adding a new entry? Remember to update simple_stmts.rst, too. @@ -115,11 +117,9 @@ language using this mechanism: .. [1] ``from __future__ import annotations`` was previously scheduled to - become mandatory in Python 3.10, but the Python Steering Council - twice decided to delay the change - (`announcement for Python 3.10 `__; - `announcement for Python 3.11 `__). - No final decision has been made yet. See also :pep:`563` and :pep:`649`. + become mandatory in Python 3.10, but the change was delayed and ultimately + canceled. This feature will eventually be deprecated and removed. See + :pep:`649` and :pep:`749`. .. seealso:: diff --git a/Doc/library/python.rst b/Doc/library/python.rst index 610435999d9f48..c2c231af7c3033 100644 --- a/Doc/library/python.rst +++ b/Doc/library/python.rst @@ -25,4 +25,5 @@ overview: __future__.rst gc.rst inspect.rst + annotationlib.rst site.rst diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 8181b9759517f6..8cf6f62a0d091b 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -1329,13 +1329,7 @@ following the parameter name. Any parameter may have an annotation, even those ``*identifier`` or ``**identifier``. Functions may have "return" annotation of the form "``-> expression``" after the parameter list. These annotations can be any valid Python expression. The presence of annotations does not change the -semantics of a function. The annotation values are available as values of -a dictionary keyed by the parameters' names in the :attr:`__annotations__` -attribute of the function object. If the ``annotations`` import from -:mod:`__future__` is used, annotations are preserved as strings at runtime which -enables postponed evaluation. Otherwise, they are evaluated when the function -definition is executed. In this case annotations may be evaluated in -a different order than they appear in the source code. +semantics of a function. See :ref:`annotations` for more information on annotations. .. index:: pair: lambda; expression @@ -1852,6 +1846,42 @@ Here, ``annotation-def`` (not a real keyword) indicates an :ref:`annotation scope `. The capitalized names like ``TYPE_PARAMS_OF_ListOrSet`` are not actually bound at runtime. +.. _annotations: + +Annotations +=========== + +.. versionchanged:: 3.14 + Annotations are now lazily evaluated by default. + +Variables and function parameters may carry :term:`annotations `, +created by adding a colon after the name, followed by an expression:: + + x: annotation = 1 + def f(param: annotation): ... + +Functions may also carry a return annotation following an arrow:: + + def f() -> annotation: ... + +Annotations are conventionally used for :term:`type hints `, but this +is not enforced by the language, and in general annotations may contain arbitrary +expressions. + +By default, annotations are lazily evaluated in a :ref:`annotation scope `. +This means that they are not evaluated when the code containing the annotation is evaluated. +Instead, the interpreter saves information that can be used to evaluate the annotation later +if requested. The :mod:`annotationlib` module provides tools for evaluating annotations. + +If the :ref:`future statement ` ``from __future__ import annotations`` is present, +all annotations are instead stored as strings:: + + >>> from __future__ import annotations + >>> def f(param: annotation): ... + >>> f.__annotations__ + {'param': 'annotation'} + + .. rubric:: Footnotes .. [#] The exception is propagated to the invocation stack unless diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 144c6f78ccd443..0d46e18bf164da 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -577,6 +577,7 @@ Special writable attributes single: __defaults__ (function attribute) single: __code__ (function attribute) single: __annotations__ (function attribute) + single: __annotate__ (function attribute) single: __kwdefaults__ (function attribute) single: __type_params__ (function attribute) @@ -624,7 +625,17 @@ Most of these attributes check the type of the assigned value: :term:`parameters `. The keys of the dictionary are the parameter names, and ``'return'`` for the return annotation, if provided. - See also: :ref:`annotations-howto`. + See also: :attr:`object.__annotations__`. + + .. versionchanged:: 3.14 + Annotations are now :ref:`lazily evaluated `. + See :pep:`649`. + + * - .. attribute:: function.__annotate__ + - The :term:`annotate function` for this function, or ``None`` + if the function has no annotations. See :attr:`object.__annotate__`. + + .. versionadded:: 3.14 * - .. attribute:: function.__kwdefaults__ - A :class:`dictionary ` containing defaults for keyword-only @@ -884,6 +895,7 @@ Attribute assignment updates the module's namespace dictionary, e.g., single: __doc__ (module attribute) single: __file__ (module attribute) single: __annotations__ (module attribute) + single: __annotate__ (module attribute) pair: module; namespace Predefined (writable) attributes: @@ -904,11 +916,21 @@ Predefined (writable) attributes: loaded dynamically from a shared library, it's the pathname of the shared library file. - :attr:`__annotations__` + :attr:`~object.__annotations__` A dictionary containing :term:`variable annotations ` collected during module body execution. For best practices on working - with :attr:`__annotations__`, please see :ref:`annotations-howto`. + with :attr:`!__annotations__`, see :mod:`annotationlib`. + + .. versionchanged:: 3.14 + Annotations are now :ref:`lazily evaluated `. + See :pep:`649`. + + :attr:`~object.__annotate__` + The :term:`annotate function` for this module, or ``None`` + if the module has no annotations. See :attr:`object.__annotate__`. + + .. versionadded:: 3.14 .. index:: single: __dict__ (module attribute) @@ -972,6 +994,7 @@ A class object can be called (see above) to yield a class instance (see below). single: __bases__ (class attribute) single: __doc__ (class attribute) single: __annotations__ (class attribute) + single: __annotate__ (class attribute) single: __type_params__ (class attribute) single: __static_attributes__ (class attribute) single: __firstlineno__ (class attribute) @@ -994,12 +1017,36 @@ Special attributes: :attr:`__doc__` The class's documentation string, or ``None`` if undefined. - :attr:`__annotations__` + :attr:`~object.__annotations__` A dictionary containing :term:`variable annotations ` collected during class body execution. For best practices on - working with :attr:`__annotations__`, please see - :ref:`annotations-howto`. + working with :attr:`~object.__annotations__`, please see + :mod:`annotationlib`. + + .. warning:: + + Accessing the :attr:`~object.__annotations__` attribute of a class + object directly may yield incorrect results in the presence of + metaclasses. Use :func:`annotationlib.get_annotations` to + retrieve class annotations safely. + + .. versionchanged:: 3.14 + Annotations are now :ref:`lazily evaluated `. + See :pep:`649`. + + :attr:`~object.__annotate__` + The :term:`annotate function` for this class, or ``None`` + if the class has no annotations. See :attr:`object.__annotate__`. + + .. warning:: + + Accessing the :attr:`~object.__annotate__` attribute of a class + object directly may yield incorrect results in the presence of + metaclasses. Use :func:`annotationlib.get_annotate_function` to + retrieve the annotate function safely. + + .. versionadded:: 3.14 :attr:`__type_params__` A tuple containing the :ref:`type parameters ` of @@ -3256,6 +3303,51 @@ implement the protocol in Python. :class:`collections.abc.Buffer` ABC for buffer types. +Annotations +----------- + +Functions, classes, and modules may contain :term:`annotations `, +which are a way to associate information (usually :term:`type hints `) +with a symbol. + +.. attribute:: object.__annotations__ + + This attribute contains the annotations for an object. It is + :ref:`lazily evaluated `, so accessing the attribute may + execute arbitrary code and raise exceptions. If evaluation is successful, the + attribute is set to a dictionary mapping from variable names to annotations. + + .. versionchanged:: 3.14 + Annotations are now lazily evaluated. + +.. method:: object.__annotate__(format) + + An :term:`annotate function`. + Returns a new dictionary object mapping attribute/parameter names to their annotation values. + + Takes a format parameter specifying the format in which annotations values should be provided. + It must be a member of the :class:`annotationlib.Format` enum, or an integer with + a value corresponding to a member of the enum. + + If an annotate function doesn't support the requested format, it must raise + :exc:`NotImplementedError()`. Annotate functions must always support + :attr:`~annotationlib.Format.VALUE` format; they must not raise + :exc:`NotImplementedError()` when called with this format. + + When called with :attr:`~annotationlib.Format.VALUE` format, an annotate function may raise + :exc:`NameError`; it must not raise :exc:`!NameError` when called requesting any other format. + + If an object does not have any annotations, :attr:`~object.__annotate__` should preferably be set + to ``None`` (it can’t be deleted), rather than set to a function that returns an empty dict. + + .. versionadded:: 3.14 + +.. seealso:: + + :pep:`649` - Deferred evaluation of annotation using descriptors + Introduces lazy evaluation of annotations and the ``__annotate__`` function. + + .. _special-lookup: Special method lookup diff --git a/Doc/reference/executionmodel.rst b/Doc/reference/executionmodel.rst index f24e1537af39ed..a02b5153ef0620 100644 --- a/Doc/reference/executionmodel.rst +++ b/Doc/reference/executionmodel.rst @@ -190,14 +190,15 @@ However, the following will succeed:: Annotation scopes ----------------- -:ref:`Type parameter lists ` and :keyword:`type` statements +:term:`Annotations `, :ref:`type parameter lists ` +and :keyword:`type` statements introduce *annotation scopes*, which behave mostly like function scopes, -but with some exceptions discussed below. :term:`Annotations ` -currently do not use annotation scopes, but they are expected to use -annotation scopes in Python 3.13 when :pep:`649` is implemented. +but with some exceptions discussed below. Annotation scopes are used in the following contexts: +* :term:`Function annotations `. +* :term:`Variable annotations `. * Type parameter lists for :ref:`generic type aliases `. * Type parameter lists for :ref:`generic functions `. A generic function's annotations are @@ -236,17 +237,23 @@ Annotation scopes differ from function scopes in the following ways: Annotation scopes are also used for type parameter defaults, as introduced by :pep:`696`. +.. versionchanged:: 3.14 + Annotation scopes are now also used for annotations, as specified in + :pep:`649` and :pep:`749`. + .. _lazy-evaluation: Lazy evaluation --------------- -The values of type aliases created through the :keyword:`type` statement are -*lazily evaluated*. The same applies to the bounds, constraints, and default values of type +Most annotation scopes are *lazily evaluated*. This includes annotations, +the values of type aliases created through the :keyword:`type` statement, and +the bounds, constraints, and default values of type variables created through the :ref:`type parameter syntax `. This means that they are not evaluated when the type alias or type variable is -created. Instead, they are only evaluated when doing so is necessary to resolve -an attribute access. +created, or when the object carrying annotations is created. Instead, they +are only evaluated when necessary, for example when the ``__value__`` +attribute on a type alias is accessed. Example: diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 618664b23f0680..24df4a6ba7b678 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -336,23 +336,21 @@ The difference from normal :ref:`assignment` is that only a single target is all The assignment target is considered "simple" if it consists of a single name that is not enclosed in parentheses. For simple assignment targets, if in class or module scope, -the annotations are evaluated and stored in a special class or module -attribute :attr:`__annotations__` -that is a dictionary mapping from variable names (mangled if private) to -evaluated annotations. This attribute is writable and is automatically -created at the start of class or module body execution, if annotations -are found statically. +the annotations are gathered in a lazily evaluated +:ref:`annotation scope `. The annotations can be +evaluated using the :attr:`~object.__annotations__` attribute of a +class or module, or using the facilities in the :mod:`annotationlib` +module. If the assignment target is not simple (an attribute, subscript node, or -parenthesized name), the annotation is evaluated if -in class or module scope, but not stored. +parenthesized name), the annotation is never evaluated. If a name is annotated in a function scope, then this name is local for that scope. Annotations are never evaluated and stored in function scopes. If the right hand side is present, an annotated -assignment performs the actual assignment before evaluating annotations -(where applicable). If the right hand side is not present for an expression +assignment performs the actual assignment as if there was no annotation +present. If the right hand side is not present for an expression target, then the interpreter evaluates the target except for the last :meth:`~object.__setitem__` or :meth:`~object.__setattr__` call. @@ -373,6 +371,10 @@ target, then the interpreter evaluates the target except for the last regular assignments. Previously, some expressions (like un-parenthesized tuple expressions) caused a syntax error. +.. versionchanged:: 3.14 + Annotations are now lazily evaluated in a separate :ref:`annotation scope `. + If the assignment target is not simple, annotations are never evaluated. + .. _assert: @@ -975,8 +977,8 @@ block textually preceding that :keyword:`!global` statement. Names listed in a :keyword:`global` statement must not be defined as formal parameters, or as targets in :keyword:`with` statements or :keyword:`except` clauses, or in a :keyword:`for` target list, :keyword:`class` -definition, function definition, :keyword:`import` statement, or variable -annotation. +definition, function definition, :keyword:`import` statement, or +:term:`variable annotations `. .. impl-detail:: From 206560c9af1c04cc0456c84f0030557d6749b94a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 23 Jul 2024 21:49:45 -0700 Subject: [PATCH 02/20] Document evaluate_forward_ref --- Doc/library/typing.rst | 29 +++++++++++++++++++++++++++++ Lib/typing.py | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 7d1d317b9f8f8a..2647bf11879fae 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -3235,6 +3235,35 @@ Introspection helpers .. versionadded:: 3.7.4 + .. versionchanged:: 3.14 + This is now an alias for :class:`annotationlib.ForwardRef`. + +.. function:: evaluate_forward_ref(forward_ref, *, owner=None, globals=None, locals=None, type_params=None, format=annotationlib.Format.VALUE) + + Evaluate an :class:`annotationlib.ForwardRef` as a :term:`type hint`. + + This is similar to calling the :meth:`annotationlib.ForwardRef.evaluate()` method, + but unlike that method, :func:`!evaluate_forward_ref` also: + + * Recursively evaluates forward references nested within the type hint. + * Rejects certain objects that are not valid type hints. + * Replaces type hints that evaluate to None with types.NoneType. + * Supports the :attr:`~annotationlib.Format.FORWARDREF` and + :attr:`~annotationlib.Format.SOURCE` formats. + + *forward_ref* must be an instance of :class:`~annotationlib.ForwardRef`. *owner*, if given, + should be the object that holds the annotations that the forward reference + derived from, such as a module, class object, or function. It is used to + infer the namespaces to use for looking up names. *globals* and *locals* + can also be explicitly given to provide the global and local namespaces. + *type_params* is a tuple of :ref:`type parameters ` that are in scope when + evaluating the forward reference. This parameter must be provided (though + it may be an empty tuple) if *owner* is not given and the forward reference + does not already have an owner set. *format* specifies the format of the + annotation and is a member of the :class:`annotationlib.Format` enum. + + .. versionadded:: 3.14 + .. data:: NoDefault A sentinel object used to indicate that a type parameter has no default diff --git a/Lib/typing.py b/Lib/typing.py index 626053d8166160..39a14ae6f83c28 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1054,7 +1054,7 @@ def evaluate_forward_ref( evaluating the forward reference. This parameter must be provided (though it may be an empty tuple) if *owner* is not given and the forward reference does not already have an owner set. *format* specifies the format of the - annotation and is a member of the annoations.Format enum. + annotation and is a member of the annotationlib.Format enum. """ if type_params is _sentinel: From 79ce456b47ed2f7c248f0b40f2698389094b050f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 24 Jul 2024 06:39:32 -0700 Subject: [PATCH 03/20] add something --- Doc/reference/compound_stmts.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 8cf6f62a0d091b..414f69d012ceea 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -1866,7 +1866,9 @@ Functions may also carry a return annotation following an arrow:: Annotations are conventionally used for :term:`type hints `, but this is not enforced by the language, and in general annotations may contain arbitrary -expressions. +expressions. The presence of annotations does not change the runtime semantics of +the code, except if some mechanism is used that introspects and uses the annotations +(such as :mod:`dataclasses` or :func:`functools.singledispatch`). By default, annotations are lazily evaluated in a :ref:`annotation scope `. This means that they are not evaluated when the code containing the annotation is evaluated. From 3f70c21899db0e4783b830527a399a22aeeb9573 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 24 Jul 2024 06:39:44 -0700 Subject: [PATCH 04/20] docs for annotationlib --- Doc/library/annotationlib.rst | 178 ++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 Doc/library/annotationlib.rst diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst new file mode 100644 index 00000000000000..0c7306dd7dae50 --- /dev/null +++ b/Doc/library/annotationlib.rst @@ -0,0 +1,178 @@ +: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 ` +on modules, classes, and functions. + +.. 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. + + Annotate functions generated by the compiler for functions, classes, and modules support only + the :attr:`~Format.VALUE` format when called directly. This function calls the annotate function + in a special environment that allows it to produce annotations in the other formats. + + *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 :class:`ForwardRef` + object that carry more information. + + .. versionadded:: 3.14 + +.. class:: Format + + A :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 + + Values are the result of evaluating the annotation expressions. + + This enum member's value is equal to 1. + + .. attribute:: FORWARDREF + + Values are real annotation values (as per :attr:`Format.VALUE` format) for defined values, + and :class:`ForwardRef` proxies for undefined values. Real objects may be exposed to, + or contain references to, :class:`ForwardRef` proxy objects. + + This enum member's value is equal to 2. + + .. attribute:: SOURCE + + Values are the text string of the annotation as it appears in the source code. + May only be approximate; whitespace may be normalized, and constant values + may be optimized. It is possible the exact values of these strings could + change in future version 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(*, globals=None, locals=None, type_params=None, owner=None) + + Evaluate the forward reference, returning its value. + + This may throw an exception such as :exc:`NameError` if the forward reference + refers to names that do not exist. The parameters to the function can be used to + provide bindings for names that would otherwise be undefined. + + *globals* and *locals* are passed to :func:`eval()`, representing the global and + local namespaces in which the name is evaluated. *type_params*, if given, must be + a tuple of :ref:`type parameters ` that are in scope while the forward + reference is being evaluated. *owner* is the object that owns the annotation from + which the forward reference derives, usually a function, class, or module. + :class:`~ForwardRef` instances returned by :func:`get_annotations` retain + a reference to their owner, so it is not necessary to pass it in explicitly. + + Once a :class:`~ForwardRef` instance has been evaluated, it caches the evaluated + value, and future calls to :meth:`evaluate` will return the cached value, regardless + of the parameters passed in. + + .. versionadded:: 3.14 + +.. function:: get_annotate_function(obj) + + Retrieve the :term:`annotate function` for *obj*. Return ``None`` if *obj* does not have an + annotate function. + + This is usually equivalent to accessing the :attr:`~object.__annotate__` attribute of *obj*, + but direct access to the attribute may return the wrong object in certain situations involving + metaclasses. It is recommended to use this function instead of accessing the attribute directly. + + .. 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__` and :attr:`~object.__annotations__` attributes. + Passing in an object of any other type raises :exc:`TypeError`. + + The *format* parameter controls the format in which annotations are returned: + + Returns a dict. ``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 ``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. + * Always, always, always returns a freshly created dict. + + ``eval_str`` controls whether or not values of type ``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 ``str``. + (Note that ``get_annotations`` doesn't catch exceptions; if :func:`eval()` + raises an exception, it will unwind the stack past the ``get_annotations`` + call.) + * If eval_str is false (the default), values of type ``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 ``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__ `, + although if ``obj`` is a wrapped function (using + :func:`functools.update_wrapper`) it is first unwrapped. + + 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': , 'b': , 'return': } + + .. versionadded:: 3.14 From e2973493c8465388ec199f3e027b04422275d41d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 24 Jul 2024 06:47:21 -0700 Subject: [PATCH 05/20] Update inspect --- Doc/library/inspect.rst | 69 ++++++++--------------------------------- Lib/inspect.py | 4 +-- 2 files changed, 14 insertions(+), 59 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 361f4054856d89..e0ecde47ce8d71 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -693,19 +693,19 @@ function. Accepts a wide range of Python callables, from plain functions and classes to :func:`functools.partial` objects. - For objects defined in modules using stringized annotations - (``from __future__ import annotations``), :func:`signature` will + If some of the annotations are strings (e.g., because + ``from __future__ import annotations`` was used), :func:`signature` will attempt to automatically un-stringize the annotations using - :func:`get_annotations`. The + :func:`annotationlib.get_annotations`. The *globals*, *locals*, and *eval_str* parameters are passed - into :func:`get_annotations` when resolving the - annotations; see the documentation for :func:`get_annotations` + into :func:`!annotationlib.get_annotations` when resolving the + annotations; see the documentation for :func:`!annotationlib.get_annotations` for instructions on how to use these parameters. Raises :exc:`ValueError` if no signature can be provided, and :exc:`TypeError` if that type of object is not supported. Also, if the annotations are stringized, and *eval_str* is not false, - the ``eval()`` call(s) to un-stringize the annotations in :func:`get_annotations` + the ``eval()`` call(s) to un-stringize the annotations in :func:`annotationlib.get_annotations` could potentially raise any kind of exception. A slash(/) in the signature of a function denotes that the parameters prior @@ -1222,62 +1222,19 @@ Classes and functions .. versionadded:: 3.4 -.. function:: get_annotations(obj, *, globals=None, locals=None, eval_str=False) +.. function:: get_annotations(obj, *, globals=None, locals=None, eval_str=False, format=annotationlib.Format.VALUE) Compute the annotations dict for an object. - ``obj`` may be a callable, class, or module. - Passing in an object of any other type raises :exc:`TypeError`. - - Returns a dict. ``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 ``str`` will - be un-stringized using :func:`eval()`. This is intended - for use with stringized annotations - (``from __future__ import annotations``). - * 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. 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. - * Always, always, always returns a freshly created dict. - - ``eval_str`` controls whether or not values of type ``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 ``str``. - (Note that ``get_annotations`` doesn't catch exceptions; if :func:`eval()` - raises an exception, it will unwind the stack past the ``get_annotations`` - call.) - * If eval_str is false (the default), values of type ``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 ``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__ `, - although if ``obj`` is a wrapped function (using - :func:`functools.update_wrapper`) it is first unwrapped. - - Calling ``get_annotations`` is best practice for accessing the - annotations dict of any object. See :ref:`annotations-howto` for - more information on annotations best practices. + This is an alias for :func:`annotationlib.get_annotations`; see the documentation + of that function for more information. .. versionadded:: 3.10 + .. versionchanged:: 3.14 + This function is now an alias for :func:`annotationlib.get_annotations`. + Calling it as ``inspect.get_annotations`` will continue to work. + .. _inspect-stack: diff --git a/Lib/inspect.py b/Lib/inspect.py index ba3ecbb87c7026..547256daa2ae9b 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -24,8 +24,6 @@ stack(), trace() - get info about frames on the stack or in a traceback signature() - get a Signature object for the callable - - get_annotations() - safely compute an object's annotations """ # This module is in the public domain. No warranties. @@ -142,7 +140,7 @@ import abc -from annotationlib import get_annotations +from annotationlib import get_annotations # re-exported import ast import dis import collections.abc From d52fb08c15654761069ad11448b8efa27ff67558 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 27 Jul 2024 09:17:38 -0700 Subject: [PATCH 06/20] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/annotationlib.rst | 23 ++++++++++++----------- Doc/reference/datamodel.rst | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index 0c7306dd7dae50..6f1e4c3b906af3 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -27,14 +27,14 @@ on modules, classes, and functions. in a special environment that allows it to produce annotations in the other formats. *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 :class:`ForwardRef` + If provided, it is used in the :attr:`~Format.FORWARDREF` format to produce a :class:`ForwardRef` object that carry more information. .. versionadded:: 3.14 .. class:: Format - A :class:`enum.IntEnum` describing the formats in which annotations can be returned. + 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. @@ -55,10 +55,11 @@ on modules, classes, and functions. .. attribute:: SOURCE - Values are the text string of the annotation as it appears in the source code. - May only be approximate; whitespace may be normalized, and constant values - may be optimized. It is possible the exact values of these strings could - change in future version of Python. + 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 @@ -113,11 +114,11 @@ on modules, classes, and functions. Compute the annotations dict for an object. - ``obj`` may be a callable, class, module, or other object with + *obj* may be a callable, class, module, or other object with :attr:`~object.__annotate__` and :attr:`~object.__annotations__` attributes. Passing in an object of any other type raises :exc:`TypeError`. - The *format* parameter controls the format in which annotations are returned: + The *format* parameter controls the format in which annotations are returned. Returns a dict. ``get_annotations()`` returns a new dict every time it's called; calling it twice on the same object will return two @@ -125,8 +126,8 @@ on modules, classes, and functions. This function handles several details for you: - * If ``eval_str`` is true, values of type ``str`` will - be un-stringized using :func:`eval()`. This is intended + * If *eval_str* is true, values of type ``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`. @@ -148,7 +149,7 @@ on modules, classes, and functions. (Note that ``get_annotations`` doesn't catch exceptions; if :func:`eval()` raises an exception, it will unwind the stack past the ``get_annotations`` call.) - * If eval_str is false (the default), values of type ``str`` are unchanged. + * If *eval_str* is false (the default), values of type ``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`` diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 0d46e18bf164da..5f45c2cf84cb0f 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -3330,7 +3330,7 @@ with a symbol. a value corresponding to a member of the enum. If an annotate function doesn't support the requested format, it must raise - :exc:`NotImplementedError()`. Annotate functions must always support + :exc:`NotImplementedError`. Annotate functions must always support :attr:`~annotationlib.Format.VALUE` format; they must not raise :exc:`NotImplementedError()` when called with this format. @@ -3344,7 +3344,7 @@ with a symbol. .. seealso:: - :pep:`649` - Deferred evaluation of annotation using descriptors + :pep:`649` --- Deferred evaluation of annotation using descriptors Introduces lazy evaluation of annotations and the ``__annotate__`` function. From 35312e716a80d6847fc57795eb60a9245d86a124 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 27 Jul 2024 09:20:55 -0700 Subject: [PATCH 07/20] More feedback --- Doc/library/annotationlib.rst | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index 6f1e4c3b906af3..4aeb9376a7e349 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -40,20 +40,19 @@ on modules, classes, and functions. :attr:`~object.__annotate__` functions. .. attribute:: VALUE + :value: 1 Values are the result of evaluating the annotation expressions. - This enum member's value is equal to 1. - .. attribute:: FORWARDREF + :value: 2 Values are real annotation values (as per :attr:`Format.VALUE` format) for defined values, and :class:`ForwardRef` proxies for undefined values. Real objects may be exposed to, or contain references to, :class:`ForwardRef` proxy objects. - This enum member's value is equal to 2. - .. attribute:: SOURCE + :value: 3 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 @@ -120,7 +119,7 @@ on modules, classes, and functions. The *format* parameter controls the format in which annotations are returned. - Returns a dict. ``get_annotations()`` returns a new dict every time + 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. @@ -140,14 +139,13 @@ on modules, classes, and functions. 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. - * Always, always, always returns a freshly created dict. ``eval_str`` controls whether or not values of type ``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 ``str``. - (Note that ``get_annotations`` doesn't catch exceptions; if :func:`eval()` - raises an exception, it will unwind the stack past the ``get_annotations`` + (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 ``str`` are unchanged. From b30abbdadbff241ff8a13d76c81d19679be13c3c Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 27 Jul 2024 09:27:04 -0700 Subject: [PATCH 08/20] Docs expansion --- Doc/library/annotationlib.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index 4aeb9376a7e349..3323587e4748d4 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -81,16 +81,21 @@ on modules, classes, and functions. Evaluate the forward reference, returning its value. This may throw an exception such as :exc:`NameError` if the forward reference - refers to names that do not exist. The parameters to the function can be used to + refers to names that do not exist. The arguments to this method can be used to provide bindings for names that would otherwise be undefined. + :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. + *globals* and *locals* are passed to :func:`eval()`, representing the global and local namespaces in which the name is evaluated. *type_params*, if given, must be a tuple of :ref:`type parameters ` that are in scope while the forward reference is being evaluated. *owner* is the object that owns the annotation from which the forward reference derives, usually a function, class, or module. - :class:`~ForwardRef` instances returned by :func:`get_annotations` retain - a reference to their owner, so it is not necessary to pass it in explicitly. Once a :class:`~ForwardRef` instance has been evaluated, it caches the evaluated value, and future calls to :meth:`evaluate` will return the cached value, regardless @@ -161,7 +166,8 @@ on modules, classes, and functions. * If ``obj`` is a callable, ``globals`` defaults to :attr:`obj.__globals__ `, although if ``obj`` is a wrapped function (using - :func:`functools.update_wrapper`) it is first unwrapped. + :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 From bcb6a9fe5a8d317f8ec81673179464e348572b6c Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 27 Jul 2024 11:24:56 -0700 Subject: [PATCH 09/20] formatting --- Doc/library/annotationlib.rst | 36 +++++++++++++++++------------------ Doc/library/typing.rst | 4 ++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index 3323587e4748d4..5f6a1ce8bd394b 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -91,7 +91,7 @@ on modules, classes, and functions. about their scope, so passing arguments to this method may be necessary to evaluate them successfully. - *globals* and *locals* are passed to :func:`eval()`, representing the global and + *globals* and *locals* are passed to :func:`eval`, representing the global and local namespaces in which the name is evaluated. *type_params*, if given, must be a tuple of :ref:`type parameters ` that are in scope while the forward reference is being evaluated. *owner* is the object that owns the annotation from @@ -105,7 +105,7 @@ on modules, classes, and functions. .. function:: get_annotate_function(obj) - Retrieve the :term:`annotate function` for *obj*. Return ``None`` if *obj* does not have an + Retrieve the :term:`annotate function` for *obj*. Return :const:`!None` if *obj* does not have an annotate function. This is usually equivalent to accessing the :attr:`~object.__annotate__` attribute of *obj*, @@ -130,12 +130,12 @@ on modules, classes, and functions. This function handles several details for you: - * If *eval_str* is true, values of type ``str`` will + * 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 + 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.) @@ -145,27 +145,27 @@ on modules, classes, and functions. * 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 ``str`` are replaced - with the result of calling :func:`eval()` on those values: + *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 ``str``. + * 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 ``str`` are unchanged. + * 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 ``None``, this function may replace that value with a context-specific + *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 + * 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__ `, - although if ``obj`` is a wrapped function (using + 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. diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 2647bf11879fae..cd35f6825956e7 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -3225,7 +3225,7 @@ Introspection helpers Class used for internal typing representation of string forward references. For example, ``List["SomeClass"]`` is implicitly transformed into - ``List[ForwardRef("SomeClass")]``. ``ForwardRef`` should not be instantiated by + ``List[ForwardRef("SomeClass")]``. :class:`!ForwardRef` should not be instantiated by a user, but may be used by introspection tools. .. note:: @@ -3242,7 +3242,7 @@ Introspection helpers Evaluate an :class:`annotationlib.ForwardRef` as a :term:`type hint`. - This is similar to calling the :meth:`annotationlib.ForwardRef.evaluate()` method, + This is similar to calling the :meth:`annotationlib.ForwardRef.evaluate` method, but unlike that method, :func:`!evaluate_forward_ref` also: * Recursively evaluates forward references nested within the type hint. From b4f6385e4e84f56457bb0af49cff5b44a5572834 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 27 Jul 2024 11:49:01 -0700 Subject: [PATCH 10/20] More documentation --- Doc/glossary.rst | 8 +++- Doc/library/annotationlib.rst | 48 ++++++++++++++++++++--- Doc/library/typing.rst | 74 +++++++++++++++++++++++++++++++++++ Lib/annotationlib.py | 2 +- 4 files changed, 124 insertions(+), 8 deletions(-) diff --git a/Doc/glossary.rst b/Doc/glossary.rst index 19e2b5615a4c49..e0e8e520ff6c93 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -39,7 +39,8 @@ Glossary annotate function A function that can be called to retrieve the :term:`annotations ` of an object. This function is accessible as the :attr:`~object.__annotate__` - attribute of functions, classes, and modules. + attribute of functions, classes, and modules. Annotate functions are a + subset of :term:`evaluate functions `. annotation A label associated with a variable, a class @@ -370,6 +371,11 @@ Glossary statements. The technique contrasts with the :term:`LBYL` style common to many other languages such as C. + evaluate function + A function that can be called to evaluate a lazily evaluated attribute + of an object, such as the value of type aliases created with the :keyword:`type` + statement. + expression A piece of syntax which can be evaluated to some value. In other words, an expression is an accumulation of expression elements like literals, diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index 5f6a1ce8bd394b..289609b60995a6 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -19,16 +19,52 @@ on modules, classes, and functions. .. 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. + 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. - Annotate functions generated by the compiler for functions, classes, and modules support only - the :attr:`~Format.VALUE` format when called directly. This function calls the annotate function - in a special environment that allows it to produce annotations in the other formats. + Annotate functions generated by the compiler for functions, classes, + and modules support only the :attr:`~Format.VALUE` format when called + directly. This function calls the annotate function in a special + environment that allows it to produce annotations in the other formats. *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 carry more information. + object that carries more information. + + .. 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) + ... + NameError: name 'undefined' is not defined + >>> call_evaluate_function(Alias.evaluate_value, Format.FORWARDREF) + ForwardRef('undefined') + >>> call_evaluate_function(Alias.evaluate_value, Format.SOURCE) + 'undefined' .. versionadded:: 3.14 diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index cd35f6825956e7..fe8b8d82552bfc 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1744,6 +1744,16 @@ without the dedicated syntax, as documented below. the bound is evaluated only when the attribute is accessed, not when the type variable is created (see :ref:`lazy-evaluation`). + .. method:: evaluate_bound + + An :term:`evaluate function` corresponding to the :attr:`~TypeVar.__bound__` attribute. + When called directly, this method supports only the :attr:`~annotationlib.Format.VALUE` + format, which is equivalent to accessing the :attr:`~TypeVar.__bound__` attribute directly, + but the method object can be passed to :func:`annotationlib.call_evaluate_function` + to evaluate the value in a different format. + + .. versionadded:: 3.14 + .. attribute:: __constraints__ A tuple containing the constraints of the type variable, if any. @@ -1754,6 +1764,16 @@ without the dedicated syntax, as documented below. the constraints are evaluated only when the attribute is accessed, not when the type variable is created (see :ref:`lazy-evaluation`). + .. method:: evaluate_constraints + + An :term:`evaluate function` corresponding to the :attr:`~TypeVar.__constraints__` attribute. + When called directly, this method supports only the :attr:`~annotationlib.Format.VALUE` + format, which is equivalent to accessing the :attr:`~TypeVar.__constraints__` attribute directly, + but the method object can be passed to :func:`annotationlib.call_evaluate_function` + to evaluate the value in a different format. + + .. versionadded:: 3.14 + .. attribute:: __default__ The default value of the type variable, or :data:`typing.NoDefault` if it @@ -1761,6 +1781,16 @@ without the dedicated syntax, as documented below. .. versionadded:: 3.13 + .. method:: evaluate_default + + An :term:`evaluate function` corresponding to the :attr:`~TypeVar.__default__` attribute. + When called directly, this method supports only the :attr:`~annotationlib.Format.VALUE` + format, which is equivalent to accessing the :attr:`~TypeVar.__default__` attribute directly, + but the method object can be passed to :func:`annotationlib.call_evaluate_function` + to evaluate the value in a different format. + + .. versionadded:: 3.14 + .. method:: has_default() Return whether or not the type variable has a default value. This is equivalent @@ -1899,6 +1929,16 @@ without the dedicated syntax, as documented below. .. versionadded:: 3.13 + .. method:: evaluate_default + + An :term:`evaluate function` corresponding to the :attr:`~TypeVarTuple.__default__` attribute. + When called directly, this method supports only the :attr:`~annotationlib.Format.VALUE` + format, which is equivalent to accessing the :attr:`~TypeVarTuple.__default__` attribute directly, + but the method object can be passed to :func:`annotationlib.call_evaluate_function` + to evaluate the value in a different format. + + .. versionadded:: 3.14 + .. method:: has_default() Return whether or not the type variable tuple has a default value. This is equivalent @@ -1995,6 +2035,16 @@ without the dedicated syntax, as documented below. .. versionadded:: 3.13 + .. method:: evaluate_default + + An :term:`evaluate function` corresponding to the :attr:`~ParamSpec.__default__` attribute. + When called directly, this method supports only the :attr:`~annotationlib.Format.VALUE` + format, which is equivalent to accessing the :attr:`~ParamSpec.__default__` attribute directly, + but the method object can be passed to :func:`annotationlib.call_evaluate_function` + to evaluate the value in a different format. + + .. versionadded:: 3.14 + .. method:: has_default() Return whether or not the parameter specification has a default value. This is equivalent @@ -2119,6 +2169,30 @@ without the dedicated syntax, as documented below. >>> Recursive.__value__ Mutually + .. method:: evaluate_value + + An :term:`evaluate function` corresponding to the :attr:`__value__` attribute. + When called directly, this method supports only the :attr:`~annotationlib.Format.VALUE` + format, which is equivalent to accessing the :attr:`__value__` attribute directly, + but the method object can be passed to :func:`annotationlib.call_evaluate_function` + to evaluate the value in a different format: + + .. doctest:: + + >>> type Alias = undefined + >>> Alias.__value__ + ... + NameError: name 'undefined' is not defined + >>> from annotationlib import Format, call_evaluate_function + >>> Alias.evaluate_value(Format.VALUE) + ... + NameError: name 'undefined' is not defined + >>> call_evaluate_function(Alias.evaluate_value, Format.FORWARDREF) + ForwardRef('undefined') + + .. versionadded:: 3.14 + + Other special directives """""""""""""""""""""""" diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py index 141e31bbf910e3..85a7338b447dbb 100644 --- a/Lib/annotationlib.py +++ b/Lib/annotationlib.py @@ -6,7 +6,7 @@ import sys import types -__all__ = ["Format", "ForwardRef", "call_annotate_function", "get_annotations"] +__all__ = ["Format", "ForwardRef", "call_annotate_function", "call_evaluate_function", "get_annotate_function", "get_annotations"] class Format(enum.IntEnum): From 6ebdce96a8fc01f45e62a1c09f6a24060eda2597 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 27 Jul 2024 11:52:40 -0700 Subject: [PATCH 11/20] Apply suggestions from code review Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/glossary.rst | 2 +- Doc/library/annotationlib.rst | 13 ++++++++----- Doc/library/typing.rst | 22 ++++++++++++---------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Doc/glossary.rst b/Doc/glossary.rst index e0e8e520ff6c93..586b318299685b 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -53,7 +53,7 @@ Glossary on modules, classes, and functions, respectively. See :term:`variable annotation`, :term:`function annotation`, :pep:`484`, - :pep:`526` and :pep:`649`, which describe this functionality. + :pep:`526`, and :pep:`649`, which describe this functionality. Also see :ref:`annotations-howto` for best practices on working with annotations. diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index 289609b60995a6..3d0f59a77cdf9a 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -133,9 +133,11 @@ on modules, classes, and functions. reference is being evaluated. *owner* is the object that owns the annotation from which the forward reference derives, usually a function, class, or module. - Once a :class:`~ForwardRef` instance has been evaluated, it caches the evaluated - value, and future calls to :meth:`evaluate` will return the cached value, regardless - of the parameters passed in. + .. important:: + + Once a :class:`~ForwardRef` instance has been evaluated, it caches the evaluated + value, and future calls to :meth:`evaluate` will return the cached value, regardless + of the parameters passed in. .. versionadded:: 3.14 @@ -146,7 +148,7 @@ on modules, classes, and functions. This is usually equivalent to accessing the :attr:`~object.__annotate__` attribute of *obj*, but direct access to the attribute may return the wrong object in certain situations involving - metaclasses. It is recommended to use this function instead of accessing the attribute directly. + metaclasses. This function should be used instead of accessing the attribute directly. .. versionadded:: 3.14 @@ -158,7 +160,8 @@ on modules, classes, and functions. :attr:`~object.__annotate__` and :attr:`~object.__annotations__` attributes. Passing in an object of any other type raises :exc:`TypeError`. - The *format* parameter controls the format in which annotations are returned. + 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. 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 diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index fe8b8d82552bfc..2e715a6413f64b 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -3325,16 +3325,18 @@ Introspection helpers * Supports the :attr:`~annotationlib.Format.FORWARDREF` and :attr:`~annotationlib.Format.SOURCE` formats. - *forward_ref* must be an instance of :class:`~annotationlib.ForwardRef`. *owner*, if given, - should be the object that holds the annotations that the forward reference - derived from, such as a module, class object, or function. It is used to - infer the namespaces to use for looking up names. *globals* and *locals* - can also be explicitly given to provide the global and local namespaces. - *type_params* is a tuple of :ref:`type parameters ` that are in scope when - evaluating the forward reference. This parameter must be provided (though - it may be an empty tuple) if *owner* is not given and the forward reference - does not already have an owner set. *format* specifies the format of the - annotation and is a member of the :class:`annotationlib.Format` enum. + *forward_ref* must be an instance of :class:`~annotationlib.ForwardRef`. + *owner*, if given, should be the object that holds the annotations that + the forward reference derived from, such as a module, class object, or function. + It is used to infer the namespaces to use for looking up names. + *globals* and *locals* can also be explicitly given to provide + the global and local namespaces. + *type_params* is a tuple of :ref:`type parameters ` that + are in scope when evaluating the forward reference. + This parameter must be provided (though it may be an empty tuple) if *owner* + is not given and the forward reference does not already have an owner set. + *format* specifies the format of the annotation and is a member of + the :class:`annotationlib.Format` enum. .. versionadded:: 3.14 From 52dc99e29be3be83f8f8fa59b42f1c2beec189a4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 27 Jul 2024 11:54:30 -0700 Subject: [PATCH 12/20] stray whitespace --- Doc/library/typing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 2e715a6413f64b..d440759612e3a8 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -3328,8 +3328,8 @@ Introspection helpers *forward_ref* must be an instance of :class:`~annotationlib.ForwardRef`. *owner*, if given, should be the object that holds the annotations that the forward reference derived from, such as a module, class object, or function. - It is used to infer the namespaces to use for looking up names. - *globals* and *locals* can also be explicitly given to provide + It is used to infer the namespaces to use for looking up names. + *globals* and *locals* can also be explicitly given to provide the global and local namespaces. *type_params* is a tuple of :ref:`type parameters ` that are in scope when evaluating the forward reference. From 61eedbd78d0a340e8e8ec829d10d5e662d7eaaa8 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 27 Jul 2024 11:56:13 -0700 Subject: [PATCH 13/20] Suggestions from Adam on evaluate_forward_ref --- Doc/library/typing.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index d440759612e3a8..1b61aeb9cdca41 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -3316,12 +3316,14 @@ Introspection helpers Evaluate an :class:`annotationlib.ForwardRef` as a :term:`type hint`. - This is similar to calling the :meth:`annotationlib.ForwardRef.evaluate` method, + This is similar to calling :meth:`annotationlib.ForwardRef.evaluate`, but unlike that method, :func:`!evaluate_forward_ref` also: * Recursively evaluates forward references nested within the type hint. - * Rejects certain objects that are not valid type hints. - * Replaces type hints that evaluate to None with types.NoneType. + * Raises :exc:`TypeError` when it encounters certain objects that are + not valid type hints. + * Replaces type hints that evaluate to :const:`!None` with + :class:`types.NoneType`. * Supports the :attr:`~annotationlib.Format.FORWARDREF` and :attr:`~annotationlib.Format.SOURCE` formats. From 27668cef6be2870a2b1f2dd35bdbeb8dcfa9f451 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 27 Jul 2024 12:01:50 -0700 Subject: [PATCH 14/20] Expand docs, based on suggestion by Adam --- Doc/library/annotationlib.rst | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index 3d0f59a77cdf9a..a6747a4d7cfaf2 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -23,14 +23,22 @@ on modules, classes, and functions. a member of the :class:`Format` enum, and return the annotations dictionary produced by the function. - Annotate functions generated by the compiler for functions, classes, - and modules support only the :attr:`~Format.VALUE` format when called - directly. This function calls the annotate function in a special - environment that allows it to produce annotations in the other formats. + 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. + *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 @@ -84,8 +92,8 @@ on modules, classes, and functions. :value: 2 Values are real annotation values (as per :attr:`Format.VALUE` format) for defined values, - and :class:`ForwardRef` proxies for undefined values. Real objects may be exposed to, - or contain references to, :class:`ForwardRef` proxy objects. + and :class:`ForwardRef` proxies for undefined values. Real objects may + contain references to, :class:`ForwardRef` proxy objects. .. attribute:: SOURCE :value: 3 From c42cb607548678a701c7776b41a0a3611ff01f6d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 9 Sep 2024 16:49:03 -0700 Subject: [PATCH 15/20] doctest? --- Doc/library/annotationlib.rst | 1 + Doc/library/typing.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index a6747a4d7cfaf2..ae040823c3c55b 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -67,6 +67,7 @@ on modules, classes, and functions. >>> 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) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 124f9afec5d7d7..cb5b46f7167376 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2262,10 +2262,12 @@ without the dedicated syntax, as documented below. >>> type Alias = undefined >>> Alias.__value__ + Traceback (most recent call last): ... NameError: name 'undefined' is not defined >>> from annotationlib import Format, 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) From 18739129de22d11b2c42fdb1bdf7de9b60c6df6a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 9 Sep 2024 17:56:55 -0700 Subject: [PATCH 16/20] expand --- Doc/howto/annotations.rst | 21 +++- Doc/library/annotationlib.rst | 179 +++++++++++++++++++++++++--------- 2 files changed, 154 insertions(+), 46 deletions(-) diff --git a/Doc/howto/annotations.rst b/Doc/howto/annotations.rst index be8c7e6c827f57..ecbecbd53be3f2 100644 --- a/Doc/howto/annotations.rst +++ b/Doc/howto/annotations.rst @@ -34,11 +34,16 @@ Accessing The Annotations Dict Of An Object In Python 3.10 And Newer Python 3.10 adds a new function to the standard library: :func:`inspect.get_annotations`. In Python versions 3.10 -and newer, calling this function is the best practice for +through 3.13, calling this function is the best practice for accessing the annotations dict of any object that supports annotations. This function can also "un-stringize" stringized annotations for you. +In Python 3.14, there is a new :mod:`annotationlib` module +with functionality for working with annotations. This +includes a :func:`annotationlib.get_annotations` function, +which supersedes :func:`inspect.get_annotations`. + If for some reason :func:`inspect.get_annotations` isn't viable for your use case, you may access the ``__annotations__`` data member manually. Best practice @@ -184,7 +189,11 @@ Best Practices For ``__annotations__`` In Any Python Version * If you do assign directly to the ``__annotations__`` member of an object, you should always set it to a ``dict`` object. -* If you directly access the ``__annotations__`` member +* You should avoid accessing ``__annotations__`` directly on any object. + Instead, use :func:`annotationlib.get_annotations` (Python 3.14+) + or :func:`inspect.get_annotations` (Python 3.10+). + +* If you do directly access the ``__annotations__`` member of an object, you should ensure that it's a dictionary before attempting to examine its contents. @@ -231,3 +240,11 @@ itself be quoted. In effect the annotation is quoted This prints ``{'a': "'str'"}``. This shouldn't really be considered a "quirk"; it's mentioned here simply because it might be surprising. + +When you use a class with a custom metaclass and access ``__annotations__`` +on the class, you may observe unexpected behavior; see +:pep:`749 <749#pep749-metaclasses>` for some examples. You can avoid these +quirks by using :func:`annotationlib.get_annotations` on Python 3.14+ or +:func:`inspect.get_annotations` on Python 3.10+. On earlier versions of +Python, you can avoid these bugs by accessing the annotations from the +class's ``__dict__`` (e.g., ``cls.__dict__.get('__annotations__', None)``). diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index ae040823c3c55b..ed04ef111683a5 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -17,65 +17,92 @@ The :mod:`!annotationlib` module provides tools for introspecting :term:`annotations ` on modules, classes, and functions. -.. function:: call_annotate_function(annotate, format, *, owner=None) +Annotations are :ref:`lazily evaluated ` 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 consumers of annotations can use to +retrieve annotations in a reliable way, even in the presence of forward references and +other edge cases. - 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 module supports retrieving annotations in three main formats (see :class:`Format`), each +of which works best for different use cases: - 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. +* :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.SOURCE` 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. - *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. +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_function` and :func:`call_annotate_function`, as well as the +:func:`call_evaluate_function` function for working with :term:`evaluate functions `. - .. seealso:: - :PEP:`PEP 649 <649#the-stringizer-and-the-fake-globals-environment>` contains - an explanation of the implementation technique used by this function. +.. seealso:: - .. versionadded:: 3.14 + :pep:`649` proposed the current model for how annotations work in Python. -.. function:: call_evaluate_function(evaluate, format, *, owner=None) + :pep:`749` expanded on various aspects of :pep:`649` and introduced the :mod:`!annotationlib` module. - 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. + :ref:`annotations-howto` provides best practices for working with annotations. - This is intended for use with the evaluate functions generated for lazily evaluated elements - related to type aliases and type parameters: + :pypi:`typing-extensions` provides a backport of :func:`get_annotations` that works on earlier + versions of Python. - * :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 +Annotation semantics +-------------------- - *owner* is the object that owns the evaluate function, such as the type alias or type variable object. +The way annotations are evaluated has changed over the history of Python 3, +and currently still depends on a :ref:`future import `. +There have been execution models for annotations: - *format* can be used to control the format in which the value is returned: +* *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. - .. doctest:: +As an example, consider the following program:: - >>> 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.SOURCE) - 'undefined' + def func(a: Cls) -> None: + print(a) - .. versionadded:: 3.14 + 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': , '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 for users using annotations +as type hints, which frequently 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 @@ -150,6 +177,70 @@ on modules, classes, and functions. .. versionadded:: 3.14 + +Functions +--------- + +.. 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.SOURCE) + 'undefined' + + .. versionadded:: 3.14 + .. function:: get_annotate_function(obj) Retrieve the :term:`annotate function` for *obj*. Return :const:`!None` if *obj* does not have an From 86001c1cf0acd3e9923df917165575e826085ed4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 9 Sep 2024 17:59:01 -0700 Subject: [PATCH 17/20] Feedback from Savannah --- Doc/library/annotationlib.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index ed04ef111683a5..48c2c42dc8ea93 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -139,7 +139,7 @@ Classes 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 + 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. @@ -152,7 +152,7 @@ Classes Evaluate the forward reference, returning its value. - This may throw an exception such as :exc:`NameError` if the forward reference + This may throw an exception, such as :exc:`NameError`, if the forward reference refers to names that do not exist. The arguments to this method can be used to provide bindings for names that would otherwise be undefined. @@ -263,23 +263,23 @@ Functions 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. - Returns a dict. :func:`!get_annotations` returns a new dict every time + 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 + 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 + 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 + 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. @@ -294,7 +294,7 @@ Functions * 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* + 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)``: @@ -309,7 +309,7 @@ Functions 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 + annotations dict of any object. See :ref:`annotations-howto` for more information on annotations best practices. .. doctest:: From c1943fabde466fe208a78ef1b7798b04d8ae9505 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 9 Sep 2024 18:06:05 -0700 Subject: [PATCH 18/20] fix link --- Doc/library/annotationlib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index 48c2c42dc8ea93..2c46099b3715c4 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -59,7 +59,7 @@ Annotation semantics -------------------- The way annotations are evaluated has changed over the history of Python 3, -and currently still depends on a :ref:`future import `. +and currently still depends on a :ref:`future import `. There have been execution models for annotations: * *Stock semantics* (default in Python 3.0 through 3.13; see :pep:`3107` and :pep:`526`): From 889f5aa14c473c601b8c7c171e309e74e5082f14 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 9 Sep 2024 18:22:37 -0700 Subject: [PATCH 19/20] shorter lines --- Doc/library/annotationlib.rst | 274 +++++++++++++++++++--------------- 1 file changed, 151 insertions(+), 123 deletions(-) diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index 2c46099b3715c4..d2e2ced621291a 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -14,46 +14,51 @@ -------------- -The :mod:`!annotationlib` module provides tools for introspecting :term:`annotations ` -on modules, classes, and functions. +The :mod:`!annotationlib` module provides tools for introspecting +:term:`annotations ` on modules, classes, and functions. Annotations are :ref:`lazily evaluated ` 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 consumers of annotations can use 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 +forward references to objects that are not yet defined when the annotation +is created. This module provides a set of low-level tools that consumers +of annotations can use 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.SOURCE` 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. +* :attr:`~Format.SOURCE` 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_function` and :func:`call_annotate_function`, as well as the -:func:`call_evaluate_function` function for working with :term:`evaluate functions `. +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_function` +and :func:`call_annotate_function`, as well as the +:func:`call_evaluate_function` function for working with +:term:`evaluate functions `. .. 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. + :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. + :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. + :pypi:`typing-extensions` provides a backport of :func:`get_annotations` + that works on earlier versions of Python. Annotation semantics -------------------- @@ -62,12 +67,14 @@ The way annotations are evaluated has changed over the history of Python 3, and currently still depends on a :ref:`future import `. 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. +* *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:: @@ -80,36 +87,41 @@ As an example, consider the following program:: 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': , '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 for users using annotations -as type hints, which frequently 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. +* 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': , '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 for users using annotations as type hints, +which frequently 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. + 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 @@ -119,16 +131,17 @@ Classes .. attribute:: FORWARDREF :value: 2 - 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. + 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:: SOURCE :value: 3 - 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. + 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. @@ -138,42 +151,46 @@ Classes 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. + 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. + 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(*, globals=None, locals=None, type_params=None, owner=None) Evaluate the forward reference, returning its value. - This may throw an exception, such as :exc:`NameError`, if the forward reference - refers to names that do not exist. The arguments to this method can be used to - provide bindings for names that would otherwise be undefined. - - :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. - - *globals* and *locals* are passed to :func:`eval`, representing the global and - local namespaces in which the name is evaluated. *type_params*, if given, must be - a tuple of :ref:`type parameters ` that are in scope while the forward - reference is being evaluated. *owner* is the object that owns the annotation from - which the forward reference derives, usually a function, class, or module. + This may throw an exception, such as :exc:`NameError`, if the forward + reference refers to names that do not exist. The arguments to this + method can be used to provide bindings for names that would otherwise + be undefined. + + :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. + + *globals* and *locals* are passed to :func:`eval`, representing + the global and local namespaces in which the name is evaluated. + *type_params*, if given, must be a tuple of + :ref:`type parameters ` that are in scope while the forward + reference is being evaluated. *owner* is the object that owns the + annotation from which the forward reference derives, usually a function, + class, or module. .. important:: - Once a :class:`~ForwardRef` instance has been evaluated, it caches the evaluated - value, and future calls to :meth:`evaluate` will return the cached value, regardless - of the parameters passed in. + Once a :class:`~ForwardRef` instance has been evaluated, it caches + the evaluated value, and future calls to :meth:`evaluate` will return + the cached value, regardless of the parameters passed in. .. versionadded:: 3.14 @@ -191,39 +208,48 @@ Functions 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. + 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. + *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. + :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. + 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: + 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. + * :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: @@ -243,12 +269,13 @@ Functions .. function:: get_annotate_function(obj) - Retrieve the :term:`annotate function` for *obj*. Return :const:`!None` if *obj* does not have an - annotate function. + Retrieve the :term:`annotate function` for *obj*. Return :const:`!None` + if *obj* does not have an annotate function. - This is usually equivalent to accessing the :attr:`~object.__annotate__` attribute of *obj*, - but direct access to the attribute may return the wrong object in certain situations involving - metaclasses. This function should be used instead of accessing the attribute directly. + This is usually equivalent to accessing the :attr:`~object.__annotate__` + attribute of *obj*, but direct access to the attribute may return the wrong + object in certain situations involving metaclasses. This function should be + used instead of accessing the attribute directly. .. versionadded:: 3.14 @@ -284,19 +311,20 @@ Functions * 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: + *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. + * 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)``: + 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 From 6dd39fb71dd8ab571553e268f22cd688c8d60cba Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 10 Sep 2024 18:24:03 -0700 Subject: [PATCH 20/20] Apply suggestions from code review Co-authored-by: Carol Willing --- Doc/howto/annotations.rst | 2 +- Doc/library/annotationlib.rst | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Doc/howto/annotations.rst b/Doc/howto/annotations.rst index ecbecbd53be3f2..e9fc563f1b5880 100644 --- a/Doc/howto/annotations.rst +++ b/Doc/howto/annotations.rst @@ -241,7 +241,7 @@ itself be quoted. In effect the annotation is quoted This prints ``{'a': "'str'"}``. This shouldn't really be considered a "quirk"; it's mentioned here simply because it might be surprising. -When you use a class with a custom metaclass and access ``__annotations__`` +If you use a class with a custom metaclass and access ``__annotations__`` on the class, you may observe unexpected behavior; see :pep:`749 <749#pep749-metaclasses>` for some examples. You can avoid these quirks by using :func:`annotationlib.get_annotations` on Python 3.14+ or diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index d2e2ced621291a..ecf56ed50b6a2a 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -19,8 +19,7 @@ The :mod:`!annotationlib` module provides tools for introspecting Annotations are :ref:`lazily evaluated ` 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 consumers -of annotations can use to retrieve annotations in a reliable way, even +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 @@ -99,8 +98,8 @@ 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 for users using annotations as type hints, -which frequently need to refer to names that are not yet defined when the +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