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,
the function will return the annotations dict. The Python
compiler then stores a reference to this function in
``__annotate`` on the object.
``__annotate__`` on the object.
Furthermore, ``__annotations__`` is redefined to be a
"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
``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
@ -425,7 +427,7 @@ particularly inconvenient for decorators that wrap existing
functions and classes, as these decorators often use closures.
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.
But class objects don't retain a reference to their globals.
: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.
Even worse, in some situations it may simply be infeasible.
For example, some libraries (e.g. TypedDict, dataclass) wrap a user
class, then merge all the annotations from all that class's base
classes together into one cumulative annotations dict. If those
annotations were stringized, calling ``eval`` on them later may
not work properly, because the globals dictionary used for the
For example, some libraries (e.g. ``typing.TypedDict``, :mod:`dataclasses`)
wrap a user class, then merge all the annotations from all that
class's base classes together into one cumulative annotations dict.
If those annotations were stringized, calling ``eval`` on them later
may not work properly, because the globals dictionary used for the
``eval`` will be the module where the *user class* was defined,
which may not be the same module where the *annotation* was
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
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,
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.
``eval()`` on MicroPython doesn't support the ``locals``
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
stringize their annotations. This is surprising behavior—unprecedented
@ -530,7 +532,7 @@ __annotate__ and __annotations__
================================
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
way.
@ -554,7 +556,7 @@ code should prefer to interact with it only via the
The callable stored in ``__annotate__`` must accept a
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
``NotImplementedError()``.
@ -599,9 +601,9 @@ Language Reference:
may raise ``NameError``; it must not raise ``NameError`` when called
requesting any other format.
If an object doesn't have any annotations, ``__annotate__``
should preferably be deleted or set to ``None``, rather than set to
a function that returns an empty dict.
If an object doesn't have any annotations, ``__annotate__`` should
preferably be set to ``None`` (it can't be deleted), rather than set to a
function that returns an empty dict.
When the Python compiler compiles an object with
annotations, it simultaneously compiles the appropriate
@ -619,7 +621,7 @@ the appropriate namespaces:
* For methods on classes, and for classes, the locals dictionary
will be the class dictionary.
* 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
``__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
for ``o.__annotations__`` is unset, and ``o.__annotate__`` is set
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.
- 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,
``format``. ``format`` specifies what format the values in the
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::
VALUE = 1
@ -713,15 +715,14 @@ the minimum and maximum supported ``format`` values::
FORMAT_MAX = SOURCE
Also, when either ``__annotations__`` or ``__annotate__``
is updated on an object, the other of those two attributes
is now out-of-date and should also either be updated or
deleted. In general, the semantics established in the
previous section ensure that this happens automatically.
However, there's one case which for all practical
purposes can't be handled automatically: when the dict cached
by ``o.__annotations__`` is itself modified, or when mutable
values inside that dict are modified.
Also, when either ``__annotations__`` or ``__annotate__`` is updated on an
object, the other of those two attributes is now out-of-date and should also
either be updated or deleted (set to ``None``, in the case of ``__annotate__``
which cannot be deleted). In general, the semantics established in the previous
section ensure that this happens automatically. However, there's one case which
for all practical purposes can't be handled automatically: when the dict cached
by ``o.__annotations__`` is itself modified, or when mutable values inside that
dict are modified.
Since this can't be handled in code, it must be handled in
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
that object's ``__annotate__`` method with a function computing
the annotations dict with your desired values. Failing that, it's
best to overwrite the object's ``__annotate__`` method with ``None``,
or delete ``__annotate__`` from the object, to prevent
``inspect.get_annotations`` from generating stale results
best to overwrite the object's ``__annotate__`` method with ``None``
to prevent ``inspect.get_annotations`` from generating stale results
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:
every time you "get" a key from it that isn't mapped,
it creates, caches, and returns a new value for that key
(as per the ``__missing__`` callback for a
``collections.defaultdict``).
(as per the ``__missing__`` callback for a dictionary).
That value is a an instance of a novel type referred to
as a "stringizer".
@ -818,13 +817,13 @@ supports ``FORWARDREF`` and ``SOURCE`` formats. Initially,
``__annotate__`` method requesting the desired format.
If that raises ``NotImplementedError``, ``inspect.get_annotations``
will construct a "fake globals" environment, then call
the object's ``__annotate__`` method
the object's ``__annotate__`` method.
* ``inspect.get_annotations`` produces ``SOURCE`` format
by creating a new empty "fake globals" dict, binding it
to the object's ``__annotate__`` method, calling that
requesting ``VALUE`` format, and
then extracting the "value" from each ``FowardRef`` object
requesting ``VALUE`` format, and then extracting the string
"value" from each ``ForwardRef`` object
in the resulting dict.
* ``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
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
incorrectly when run in this "fake globals" environment.
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
those objects can take full advantage of annotations.
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.
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
or faster than stock. :pep:`563` semantics requires invoking
``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
semantics; for function annotations, this PEP and stock semantics
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
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
that behaves differently based on whether annotations are
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
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
syntax are run before the class name is bound, they can cause
unsolvable circular-definition problems. If you annotate attributes