Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PEP 749: Add a FAKE_GLOBALS_VALUE format #3991

Merged
merged 4 commits into from
Sep 25, 2024
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 46 additions & 25 deletions peps/pep-0749.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ specification:
* We specify the behavior of wrapper objects that provide annotations, such as :py:func:`classmethod`
and code that uses :py:func:`functools.wraps`.
* 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,
``FAKE_GLOBALS_VALUE``, to allow third-party implementors of annotate functions to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took me a bit to work out that this name was a combination of describing the output format and a difference in the supported inputs.

How would you feel about a more explicit separation of the two aspects, calling this format VALUE_WITH_FAKE_GLOBALS?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that, I'll ask Larry tomorrow to see if he agrees.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reviewing how the fake globals format is used, FORWARDREF_VIA_GLOBALS is likely clearer, since this format injects fake globals to produce a FORWARDREF result that can be postprocessed to give a SOURCE result.

It doesn't emit the VALUE format, so having that in the name would be misleading.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel it makes more sense to think of the format as "VALUE with an asterisk", since that's how __annotate__ functions should handle it internally.

this format injects fake globals to produce a FORWARDREF result that can be postprocessed to give a SOURCE result

That's not really how the implementation works. There isn't a forwardref involved in producing {"x": "int"} out of the annotations for def f(x: int): pass in SOURCE format.

indicate what formats they support.
* 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
(which were added by :pep:`695` and :pep:`696`) using PEP 649-like semantics.
Expand Down Expand Up @@ -74,6 +76,11 @@ We suggest the following deprecation plan:

- In Python 3.14, ``from __future__ import annotations`` will continue to work as it
did before, converting annotations into strings.

- If the future import is active, the ``__annotate__`` function of objects with
hugovk marked this conversation as resolved.
Show resolved Hide resolved
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)
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
Expand Down Expand Up @@ -194,7 +201,8 @@ The module will contain the following functionality:
* ``Format``: an enum that contains the possible formats of annotations. This will
replace the ``VALUE``, ``FORWARDREF``, and ``SOURCE`` formats in :pep:`649`.
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,
``FAKE_GLOBALS_VALUE`` (see below).
* ``ForwardRef``: a class representing a forward reference; it may be returned by
``get_annotations()`` when the format is ``FORWARDREF``. The existing class
:py:class:`typing.ForwardRef` will become an alias of this class. Its members include:
Expand Down Expand Up @@ -241,6 +249,9 @@ What should this module be called? Some ideas:
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`,
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
obvious abbreviation than "iter" or "func". As of this writing, there
is no PyPI package with this name.
Expand Down Expand Up @@ -551,8 +562,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,
it is a long-standing feature of Python and is relied upon by some users.

Remove code flag for marking ``__annotate__`` functions
=======================================================
Adding the ``FAKE_GLOBALS_VALUE`` format
========================================

:pep:`649` specifies:

Expand All @@ -567,33 +578,42 @@ Remove code flag for marking ``__annotate__`` functions
it's expected that only ``__annotate__`` methods generated
by the Python compiler will set it.

We have not found a need for this mechanism during our work to
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
However, this mechanism couples the implementation with
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>`
against relying on the values.

Larry Hastings suggested an alternative approach that does not
rely on code flags: a fourth format, ``FAKE_GLOBALS_VALUE``.
Compiler-generated annotate functions would support only the
``VALUE`` and ``FAKE_GLOBALS_VALUE`` formats, both of which are
implemented identically. The standard library would use the
``FAKE_GLOBALS_VALUE`` 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 ``FAKE_GLOBALS_VALUE`` format is requested, so the standard
library will not call the manually written annotate function with
"fake globals", which could have unpredictable results.

Specification
-------------

The standard library will use the "fake globals" technique on any
``__annotate__`` function that raises :py:exc:`NotImplementedError`
when the requested format is not supported.
An additional format, ``FAKE_GLOBALS_VALUE``, is added to the ``Format`` enum in the
``annotationlib`` module, with value equal to 4. Compiler-generated
annotate functions will support this format and return the same value as
they would return for the ``VALUE`` format. The standard library will pass
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 either
support all three annotation formats, or be prepared to handle the
"fake globals" environment. This should be mentioned in the data model
documentation for ``__annotate__``.
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__``
=====================================
Expand Down Expand Up @@ -709,7 +729,7 @@ Signature of ``__annotate__`` functions
``__annotate__(format: int) -> dict``

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
to avoid this problem.

Expand Down Expand Up @@ -858,7 +878,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,
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
========
Expand Down