PEP 749: Add a FAKE_GLOBALS_VALUE format (#3991)

Co-authored-by: Carl Meyer <carl@oddbird.net>
This commit is contained in:
Jelle Zijlstra 2024-09-25 16:45:59 -07:00 committed by GitHub
parent 6fe0175e3d
commit 9c9c5c3aee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 50 additions and 25 deletions

View File

@ -27,7 +27,9 @@ specification:
* We specify the behavior of wrapper objects that provide annotations, such as :py:func:`classmethod` * We specify the behavior of wrapper objects that provide annotations, such as :py:func:`classmethod`
and code that uses :py:func:`functools.wraps`. and code that uses :py:func:`functools.wraps`.
* There will not be a code flag for marking ``__annotate__`` functions * There will not be a code flag for marking ``__annotate__`` functions
that can be run in a "fake globals" environment. that can be run in a "fake globals" environment. Instead, we add a fourth format,
``VALUE_WITH_FAKE_GLOBALS``, to allow third-party implementors of annotate functions to
indicate what formats they support.
* Setting the ``__annotations__`` attribute directly will not affect the ``__annotate__`` attribute. * Setting the ``__annotations__`` attribute directly will not affect the ``__annotate__`` attribute.
* We add functionality to allow evaluating type alias values and type parameter bounds and defaults * We add functionality to allow evaluating type alias values and type parameter bounds and defaults
(which were added by :pep:`695` and :pep:`696`) using PEP 649-like semantics. (which were added by :pep:`695` and :pep:`696`) using PEP 649-like semantics.
@ -74,6 +76,11 @@ We suggest the following deprecation plan:
- In Python 3.14, ``from __future__ import annotations`` will continue to work as it - In Python 3.14, ``from __future__ import annotations`` will continue to work as it
did before, converting annotations into strings. did before, converting annotations into strings.
- If the future import is active, the ``__annotate__`` function of objects with
annotations will return the annotations as strings when called with the ``VALUE``
format, reflecting the behavior of ``__annotations__``.
- Sometime after the last release that did not support :pep:`649` semantics (expected to be 3.13) - Sometime after the last release that did not support :pep:`649` semantics (expected to be 3.13)
reaches its end-of-life, ``from __future__ import annotations`` is deprecated. Compiling reaches its end-of-life, ``from __future__ import annotations`` is deprecated. Compiling
any code that uses the future import will emit a :py:exc:`DeprecationWarning`. This will any code that uses the future import will emit a :py:exc:`DeprecationWarning`. This will
@ -194,7 +201,8 @@ The module will contain the following functionality:
* ``Format``: an enum that contains the possible formats of annotations. This will * ``Format``: an enum that contains the possible formats of annotations. This will
replace the ``VALUE``, ``FORWARDREF``, and ``SOURCE`` formats in :pep:`649`. replace the ``VALUE``, ``FORWARDREF``, and ``SOURCE`` formats in :pep:`649`.
PEP 649 proposed to make these values global members of the :py:mod:`inspect` PEP 649 proposed to make these values global members of the :py:mod:`inspect`
module; we prefer to place them within an enum. module; we prefer to place them within an enum. We propose to add a fourth format,
``VALUE_WITH_FAKE_GLOBALS`` (see below).
* ``ForwardRef``: a class representing a forward reference; it may be returned by * ``ForwardRef``: a class representing a forward reference; it may be returned by
``get_annotations()`` when the format is ``FORWARDREF``. The existing class ``get_annotations()`` when the format is ``FORWARDREF``. The existing class
:py:class:`typing.ForwardRef` will become an alias of this class. Its members include: :py:class:`typing.ForwardRef` will become an alias of this class. Its members include:
@ -251,6 +259,9 @@ What should this module be called? Some ideas:
and ``from __future__ import annotations`` in the same module. The use of a common word and ``from __future__ import annotations`` in the same module. The use of a common word
as the name will make the module harder to search for. There is a PyPI package :pypi:`annotations`, as the name will make the module harder to search for. There is a PyPI package :pypi:`annotations`,
but it had only a single release in 2015 and looks abandoned. but it had only a single release in 2015 and looks abandoned.
- ``annotation`` (in the singular): Similar, but does not cause confusion with the future
import. There is an abandoned PyPI package :pypi:`annotation`, but it apparently never
released any artifacts.
- ``annotools``: Analogous to :py:mod:`itertools` and :py:mod:`functools`, but "anno" is a less - ``annotools``: Analogous to :py:mod:`itertools` and :py:mod:`functools`, but "anno" is a less
obvious abbreviation than "iter" or "func". As of this writing, there obvious abbreviation than "iter" or "func". As of this writing, there
is no PyPI package with this name. is no PyPI package with this name.
@ -561,8 +572,8 @@ This approach would also mean that accessing ``.__annotations__`` on an instance
of an annotated class no longer works. While this behavior is not documented, of an annotated class no longer works. While this behavior is not documented,
it is a long-standing feature of Python and is relied upon by some users. it is a long-standing feature of Python and is relied upon by some users.
Remove code flag for marking ``__annotate__`` functions Adding the ``VALUE_WITH_FAKE_GLOBALS`` format
======================================================= =============================================
:pep:`649` specifies: :pep:`649` specifies:
@ -577,33 +588,46 @@ Remove code flag for marking ``__annotate__`` functions
it's expected that only ``__annotate__`` methods generated it's expected that only ``__annotate__`` methods generated
by the Python compiler will set it. by the Python compiler will set it.
We have not found a need for this mechanism during our work to However, this mechanism couples the implementation with
add :pep:`649` support to the standard library. While it is true
that custom ``__annotate__`` functions may not work well with the
"fake globals" environment, this technique is used only when the
``__annotate__`` function raises :py:exc:`NotImplementedError` to
signal that it does not support the requested format. However,
manually implemented ``__annotate__`` functions are likely to support
all three annotation formats; often, they will consist of a call to
``annotationlib.call_annotate_function`` plus some transformation of the
result.
In addition, the proposed mechanism couples the implementation with
low-level details of the code object. The code object flags are low-level details of the code object. The code object flags are
CPython-specific and the documentation :py:ref:`explicitly warns <inspect-module-co-flags>` CPython-specific and the documentation :py:ref:`explicitly warns <inspect-module-co-flags>`
against relying on the values. against relying on the values.
Larry Hastings suggested an alternative approach that does not
rely on code flags: a fourth format, ``VALUE_WITH_FAKE_GLOBALS``.
Compiler-generated annotate functions would support only the
``VALUE`` and ``VALUE_WITH_FAKE_GLOBALS`` formats, both of which are
implemented identically. The standard library would use the
``VALUE_WITH_FAKE_GLOBALS`` format when invoking an annotate function
in one of the special "fake globals" environments.
This approach is useful as a forward-compatible mechanism for
adding new annotation formats in the future. Users who manually
write annotate functions should raise ``NotImplementedError`` if
the ``VALUE_WITH_FAKE_GLOBALS`` format is requested, so the standard
library will not call the manually written annotate function with
"fake globals", which could have unpredictable results.
Specification Specification
------------- -------------
The standard library will use the "fake globals" technique on any An additional format, ``FAKE_GLOBALS_VALUE``, is added to the ``Format`` enum in the
``__annotate__`` function that raises :py:exc:`NotImplementedError` ``annotationlib`` module, with value equal to 2. (As a result, the values of the
when the requested format is not supported. other formats will shift relative to PEP 649: ``FORWARDREF`` will be 3 and ``SOURCE``
will be 4.)
Third-party code that implements ``__annotate__`` functions should either Compiler-generated
support all three annotation formats, or be prepared to handle the annotate functions will support this format and return the same value as
"fake globals" environment. This should be mentioned in the data model they would return for the ``VALUE`` format. The standard library will pass
documentation for ``__annotate__``. this format to the ``__annotate__`` function when it is called in a "fake globals"
environment, as used to implement the ``FORWARDREF`` and ``SOURCE`` formats.
All public functions in the ``annotationlib`` module that accept a format
argument will raise :py:exc:`NotImplementedError` if the format is ``FAKE_GLOBALS_VALUE``.
Third-party code that implements ``__annotate__`` functions should raise
:py:exc:`NotImplementedError` if the ``FAKE_GLOBALS_VALUE`` format is passed
and the function is not prepared to be run in a "fake globals" environment.
This should be mentioned in the data model documentation for ``__annotate__``.
Effect of setting ``__annotations__`` Effect of setting ``__annotations__``
===================================== =====================================
@ -754,7 +778,7 @@ Signature of ``__annotate__`` functions
``__annotate__(format: int) -> dict`` ``__annotate__(format: int) -> dict``
However, using ``format`` as a parameter name could lead to collisions However, using ``format`` as a parameter name could lead to collisions
if an annotation uses a class named ``format``. The parameter should be if an annotation uses a symbol named ``format``. The parameter should be
positional-only and have a name that cannot be a legal identifier in order positional-only and have a name that cannot be a legal identifier in order
to avoid this problem. to avoid this problem.
@ -846,7 +870,8 @@ initial decisions, but the overall design is still his.
I thank Carl Meyer and Alex Waygood for feedback on early drafts of this PEP. Alex Waygood, I thank Carl Meyer and Alex Waygood for feedback on early drafts of this PEP. Alex Waygood,
Alyssa Coghlan, and David Ellis provided insightful feedback and suggestions on the Alyssa Coghlan, and David Ellis provided insightful feedback and suggestions on the
interaction between metaclasses and ``__annotations__``. interaction between metaclasses and ``__annotations__``. Larry Hastings also provided useful
feedback on this PEP.
Appendix Appendix
======== ========