PEP 649: typo fixes and minor clarifications (#3113)

* PEP 649: typo fixes and minor clarifications

* review comments
This commit is contained in:
Carl Meyer 2023-04-22 23:50:38 -06:00 committed by GitHub
parent 468f3ed4eb
commit 076f44117a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 39 additions and 40 deletions

View File

@ -55,7 +55,7 @@ annotations, the Python compiler will write the expressions
computing the annotations into its own function. When run, computing the annotations into its own function. When run,
the function will return the annotations dict. The Python the function will return the annotations dict. The Python
compiler then stores a reference to this function in compiler then stores a reference to this function in
``__annotate`` on the object. ``__annotate__`` on the object.
Furthermore, ``__annotations__`` is redefined to be a Furthermore, ``__annotations__`` is redefined to be a
"data descriptor" which calls this annotation function once "data descriptor" which calls this annotation function once
@ -362,7 +362,9 @@ traditionally better served with stock semantics. This use case is
largely incompatible with :pep:`563`, particularly with the largely incompatible with :pep:`563`, particularly with the
``if typing.TYPE_CHECKING`` idiom. ``if typing.TYPE_CHECKING`` idiom.
Under this PEP, runtime annotation users will use ``VALUE`` format. Under this PEP, runtime annotation users will most likely prefer ``VALUE``
format, though some (e.g. if they evaluate annotations eagerly in a decorator
and want to support forward references) may also use ``FORWARDREF`` format.
Wrappers Wrappers
@ -425,7 +427,7 @@ particularly inconvenient for decorators that wrap existing
functions and classes, as these decorators often use closures. functions and classes, as these decorators often use closures.
Second, in order for ``eval`` to correctly look up globals in a Second, in order for ``eval`` to correctly look up globals in a
stringized annotation, you must first obtaining a reference stringized annotation, you must first obtain a reference
to the correct module. to the correct module.
But class objects don't retain a reference to their globals. But class objects don't retain a reference to their globals.
:pep:`563` suggests looking up a class's module by name in :pep:`563` suggests looking up a class's module by name in
@ -437,11 +439,11 @@ difficult to determine the correct globals and locals dicts to
give to ``eval`` to properly evaluate a stringized annotation. give to ``eval`` to properly evaluate a stringized annotation.
Even worse, in some situations it may simply be infeasible. Even worse, in some situations it may simply be infeasible.
For example, some libraries (e.g. TypedDict, dataclass) wrap a user For example, some libraries (e.g. ``typing.TypedDict``, :mod:`dataclasses`)
class, then merge all the annotations from all that class's base wrap a user class, then merge all the annotations from all that
classes together into one cumulative annotations dict. If those class's base classes together into one cumulative annotations dict.
annotations were stringized, calling ``eval`` on them later may If those annotations were stringized, calling ``eval`` on them later
not work properly, because the globals dictionary used for the may not work properly, because the globals dictionary used for the
``eval`` will be the module where the *user class* was defined, ``eval`` will be the module where the *user class* was defined,
which may not be the same module where the *annotation* was which may not be the same module where the *annotation* was
defined. However, if the annotations were stringized because defined. However, if the annotations were stringized because
@ -464,7 +466,7 @@ of that string will fail. This is a problem for libraries with
that need to examine the annotation, because they can't reliably that need to examine the annotation, because they can't reliably
convert these stringized annotations into real values. convert these stringized annotations into real values.
* Some libraries (e.g. ``dataclass``) solved this by foregoing real * Some libraries (e.g. :mod:`dataclasses`) solved this by foregoing real
values and performing lexical analysis of the stringized annotation, values and performing lexical analysis of the stringized annotation,
which requires a lot of work to get right. which requires a lot of work to get right.
@ -476,7 +478,7 @@ Also, ``eval()`` is slow, and it isn't always available; it's
sometimes removed for space reasons on certain platforms. sometimes removed for space reasons on certain platforms.
``eval()`` on MicroPython doesn't support the ``locals`` ``eval()`` on MicroPython doesn't support the ``locals``
argument, which makes converting stringized annotations argument, which makes converting stringized annotations
into real values at runtime even harder.. into real values at runtime even harder.
Finally, :pep:`563` requires Python implementations to Finally, :pep:`563` requires Python implementations to
stringize their annotations. This is surprising behavior—unprecedented stringize their annotations. This is surprising behavior—unprecedented
@ -530,7 +532,7 @@ __annotate__ and __annotations__
================================ ================================
Python supports annotations on three different types: Python supports annotations on three different types:
function, classes, and modules. This PEP modifies functions, classes, and modules. This PEP modifies
the semantics on all three of these types in a similar the semantics on all three of these types in a similar
way. way.
@ -554,7 +556,7 @@ code should prefer to interact with it only via the
The callable stored in ``__annotate__`` must accept a The callable stored in ``__annotate__`` must accept a
single required positional argument called ``format``, single required positional argument called ``format``,
which will always be a ``int``. It must either return which will always be an ``int``. It must either return
a dict (or subclass of dict) or raise a dict (or subclass of dict) or raise
``NotImplementedError()``. ``NotImplementedError()``.
@ -599,9 +601,9 @@ Language Reference:
may raise ``NameError``; it must not raise ``NameError`` when called may raise ``NameError``; it must not raise ``NameError`` when called
requesting any other format. requesting any other format.
If an object doesn't have any annotations, ``__annotate__`` If an object doesn't have any annotations, ``__annotate__`` should
should preferably be deleted or set to ``None``, rather than set to preferably be set to ``None`` (it can't be deleted), rather than set to a
a function that returns an empty dict. function that returns an empty dict.
When the Python compiler compiles an object with When the Python compiler compiles an object with
annotations, it simultaneously compiles the appropriate annotations, it simultaneously compiles the appropriate
@ -619,7 +621,7 @@ the appropriate namespaces:
* For methods on classes, and for classes, the locals dictionary * For methods on classes, and for classes, the locals dictionary
will be the class dictionary. will be the class dictionary.
* If the annotations refer to free variables, the closure will * If the annotations refer to free variables, the closure will
be the appropriate tuple containing free variables. be the appropriate closure tuple containing cells for free variables.
Second, this PEP requires that the existing Second, this PEP requires that the existing
``__annotations__`` must be a "data descriptor", ``__annotations__`` must be a "data descriptor",
@ -644,7 +646,7 @@ an object of any of these three types:
* When ``o.__annotations__`` is evaluated, and the internal storage * When ``o.__annotations__`` is evaluated, and the internal storage
for ``o.__annotations__`` is unset, and ``o.__annotate__`` is set for ``o.__annotations__`` is unset, and ``o.__annotate__`` is set
to a callable, the getter for ``o.__annotations__`` calls to a callable, the getter for ``o.__annotations__`` calls
``o.__annotate__(1)``, then caches the result in its intenral ``o.__annotate__(1)``, then caches the result in its internal
storage and returns the result. storage and returns the result.
- To explicitly clarify one question that has come up multiple times: - To explicitly clarify one question that has come up multiple times:
@ -695,7 +697,7 @@ makes some modifications to the annotations before it returns them.
This PEP adds a new keyword-only parameter to these two functions, This PEP adds a new keyword-only parameter to these two functions,
``format``. ``format`` specifies what format the values in the ``format``. ``format`` specifies what format the values in the
annotations dict should be returned in. annotations dict should be returned in.
``format`` accepts following values, defined as attributes on the ``format`` accepts the following values, defined as attributes on the
``inspect`` module:: ``inspect`` module::
VALUE = 1 VALUE = 1
@ -713,15 +715,14 @@ the minimum and maximum supported ``format`` values::
FORMAT_MAX = SOURCE FORMAT_MAX = SOURCE
Also, when either ``__annotations__`` or ``__annotate__`` Also, when either ``__annotations__`` or ``__annotate__`` is updated on an
is updated on an object, the other of those two attributes object, the other of those two attributes is now out-of-date and should also
is now out-of-date and should also either be updated or either be updated or deleted (set to ``None``, in the case of ``__annotate__``
deleted. In general, the semantics established in the which cannot be deleted). In general, the semantics established in the previous
previous section ensure that this happens automatically. section ensure that this happens automatically. However, there's one case which
However, there's one case which for all practical for all practical purposes can't be handled automatically: when the dict cached
purposes can't be handled automatically: when the dict cached by ``o.__annotations__`` is itself modified, or when mutable values inside that
by ``o.__annotations__`` is itself modified, or when mutable dict are modified.
values inside that dict are modified.
Since this can't be handled in code, it must be handled in Since this can't be handled in code, it must be handled in
documentation. This PEP proposes amending the documentation documentation. This PEP proposes amending the documentation
@ -735,9 +736,8 @@ for ``inspect.get_annotations`` (and similarly for
modifying the ``__annotations__`` dict directly, consider replacing modifying the ``__annotations__`` dict directly, consider replacing
that object's ``__annotate__`` method with a function computing that object's ``__annotate__`` method with a function computing
the annotations dict with your desired values. Failing that, it's the annotations dict with your desired values. Failing that, it's
best to overwrite the object's ``__annotate__`` method with ``None``, best to overwrite the object's ``__annotate__`` method with ``None``
or delete ``__annotate__`` from the object, to prevent to prevent ``inspect.get_annotations`` from generating stale results
``inspect.get_annotations`` from generating stale results
for ``SOURCE`` and ``FORWARDREF`` formats. for ``SOURCE`` and ``FORWARDREF`` formats.
@ -762,8 +762,7 @@ dict is replaced with what's called a "fake globals" dict.
A "fake globals" dict is a dict with one important difference: A "fake globals" dict is a dict with one important difference:
every time you "get" a key from it that isn't mapped, every time you "get" a key from it that isn't mapped,
it creates, caches, and returns a new value for that key it creates, caches, and returns a new value for that key
(as per the ``__missing__`` callback for a (as per the ``__missing__`` callback for a dictionary).
``collections.defaultdict``).
That value is a an instance of a novel type referred to That value is a an instance of a novel type referred to
as a "stringizer". as a "stringizer".
@ -818,13 +817,13 @@ supports ``FORWARDREF`` and ``SOURCE`` formats. Initially,
``__annotate__`` method requesting the desired format. ``__annotate__`` method requesting the desired format.
If that raises ``NotImplementedError``, ``inspect.get_annotations`` If that raises ``NotImplementedError``, ``inspect.get_annotations``
will construct a "fake globals" environment, then call will construct a "fake globals" environment, then call
the object's ``__annotate__`` method the object's ``__annotate__`` method.
* ``inspect.get_annotations`` produces ``SOURCE`` format * ``inspect.get_annotations`` produces ``SOURCE`` format
by creating a new empty "fake globals" dict, binding it by creating a new empty "fake globals" dict, binding it
to the object's ``__annotate__`` method, calling that to the object's ``__annotate__`` method, calling that
requesting ``VALUE`` format, and requesting ``VALUE`` format, and then extracting the string
then extracting the "value" from each ``FowardRef`` object "value" from each ``ForwardRef`` object
in the resulting dict. in the resulting dict.
* ``inspect.get_annotations`` produces ``FORWARDREF`` format * ``inspect.get_annotations`` produces ``FORWARDREF`` format
@ -845,7 +844,7 @@ methods, this approach is a reliable, practical solution.
However, it's not reasonable to attempt this technique with However, it's not reasonable to attempt this technique with
just any ``__annotate__`` method. This PEP assumes that just any ``__annotate__`` method. This PEP assumes that
third-party libraries will implement their own ``__annotate__`` third-party libraries may implement their own ``__annotate__``
methods, and those functions would almost certainly work methods, and those functions would almost certainly work
incorrectly when run in this "fake globals" environment. incorrectly when run in this "fake globals" environment.
For that reason, this PEP allocates a flag on code objects, For that reason, this PEP allocates a flag on code objects,
@ -928,7 +927,7 @@ to implement their own ``__annotate__`` methods,
so that downstream users of so that downstream users of
those objects can take full advantage of annotations. those objects can take full advantage of annotations.
In particular, wrappers will likely need to transform In particular, wrappers will likely need to transform
the annotation dicts produced by the wrapped object--adding, the annotation dicts produced by the wrapped object: adding,
removing, or modifying the dictionary in some way. removing, or modifying the dictionary in some way.
Most of the time, third-party code will implement Most of the time, third-party code will implement
@ -1117,7 +1116,7 @@ When annotations are both defined and referenced, code using
this PEP should be much faster than :pep:`563`, and be as fast this PEP should be much faster than :pep:`563`, and be as fast
or faster than stock. :pep:`563` semantics requires invoking or faster than stock. :pep:`563` semantics requires invoking
``eval()`` for every value inside an annotations dict which is ``eval()`` for every value inside an annotations dict which is
enormously slow. And the implementation of PEP generates measurably enormously slow. And the implementation of this PEP generates measurably
more efficient bytecode for class and module annotations than stock more efficient bytecode for class and module annotations than stock
semantics; for function annotations, this PEP and stock semantics semantics; for function annotations, this PEP and stock semantics
should be about the same speed. should be about the same speed.
@ -1193,7 +1192,7 @@ Python version the code runs under.
Since delaying the evaluation of annotations until they are Since delaying the evaluation of annotations until they are
evaluated changes the semantics of the language, it's observable introspected changes the semantics of the language, it's observable
from within the language. Therefore it's *possible* to write code from within the language. Therefore it's *possible* to write code
that behaves differently based on whether annotations are that behaves differently based on whether annotations are
evaluated at binding time or at access time, e.g. evaluated at binding time or at access time, e.g.
@ -1312,7 +1311,7 @@ dicts; however, users should consider switching to
Similarly, :pep:`563` permitted use of class decorators on Similarly, :pep:`563` permitted use of class decorators on
annotated classes in a way that hadn't previously been possible. annotated classes in a way that hadn't previously been possible.
Some class decorators (e.g. ``dataclasses``) examine the annotations Some class decorators (e.g. :mod:`dataclasses`) examine the annotations
on the class. Because class decorators using the ``@`` decorator on the class. Because class decorators using the ``@`` decorator
syntax are run before the class name is bound, they can cause syntax are run before the class name is bound, they can cause
unsolvable circular-definition problems. If you annotate attributes unsolvable circular-definition problems. If you annotate attributes