From 076f44117a596ba84ec323c6db697daef3bb0dcb Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 22 Apr 2023 23:50:38 -0600 Subject: [PATCH] PEP 649: typo fixes and minor clarifications (#3113) * PEP 649: typo fixes and minor clarifications * review comments --- pep-0649.rst | 79 ++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/pep-0649.rst b/pep-0649.rst index a45ebe734..c0189a838 100644 --- a/pep-0649.rst +++ b/pep-0649.rst @@ -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