8000 PEP 749: Add section on metaclasses (#3847) · pauleveritt/peps@e248a85 · GitHub
[go: up one dir, main page]

Skip to content

Commit e248a85

Browse files
JelleZijlstracarljm
authored andcommitted
PEP 749: Add section on metaclasses (python#3847)
Co-authored-by: Carl Meyer <carl@oddbird.net>
1 parent 7368a3d commit e248a85

File tree

1 file changed

+164
-1
lines changed

1 file changed

+164
-1
lines changed

peps/pep-0749.rst

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ The module will contain the following functionality:
187187
module, or class. This will replace :py:func:`inspect.get_annotations`. The latter
188188
will delegate to the new function. It may eventually be deprecated, but to
189189
minimize disruption, we do not propose an immediate deprecation.
190+
* ``get_annotate_function()``: A function that returns the ``__annotate__`` function
191+
of an object, if it has one, or ``None`` if it does not. This is usually equivalent
192+
to accessing the ``.__annotate__`` attribute, except in the presence of metaclasses
193+
(see :ref:`below <pep749-metaclasses>`).
190194
* ``Format``: an enum that contains the possible formats of annotations. This will
191195
replace the ``VALUE``, ``FORWARDREF``, and ``SOURCE`` formats in :pep:`649`.
192196
PEP 649 proposed to make these values global members of the :py:mod:`inspect`
@@ -390,6 +394,163 @@ More specifically:
390394
``__dict__``. Writing to these attributes will directly update the ``__dict__``,
391395
without affecting the wrapped callable.
392396

397+
.. _pep749-metaclasses:
398+
399+
Annotations and metaclasses
400+
===========================
401+
402+
Testing of the initial implementation of this PEP revealed serious problems with
403+
the interaction between metaclasses and class annotations.
404+
405+
Pre-existing bugs
406+
-----------------
407+
408+
We found several bugs in the existing behavior of ``__annotations__`` on classes
409+
while investigating the behaviors to be specified in this PEP. Fixing these bugs
410+
on Python 3.13 and earlier is outside the scope of this PEP, but they are noted here
411+
to explain the corner cases that need to be dealt with.
412+
413+
For context, on Python 3.10 through 3.13 the ``__annotations__`` dictionary is
414+
placed in the class namespace if the class has any annotations. If it does not,
415+
there is no ``__annotations__`` class dictionary key when the class is created,
416+
but accessing ``cls.__annotations__`` invokes a descriptor defined on ``type``
417+
that returns an empty dictionary and stores it in the class dictionary.
418+
:py:ref:`Static types <static-types>` are an exception: they never have
419+
annotations, and accessing ``.__annotations__`` raises :py:exc:`AttributeError`.
420+
On Python 3.9 and earlier, the behavior was different; see
421+
`gh-88067 <https://github.com/python/cpython/issues/88067>`__.
422+
423+
The following code fails identically on Python 3.10 through 3.13::
424+
425+
class Meta(type): pass
426+
427+
class X(metaclass=Meta):
428+
a: str
429+
430+
class Y(X): pass
431+
432+
Meta.__annotations__ # important
433+
assert Y.__annotations__ == {}, Y.__annotations__ # fails: {'a': <class 'str'>}
434+
435+
If the annotations on the metaclass ``Meta`` are accessed before the annotations
436+
on ``Y``, then the annotations for the base class ``X`` are leaked to ``Y``.
437+
However, if the metaclass's annotations are *not* accessed (i.e., the line ``Meta.__annotations__``
438+
above is removed), then the annotations for ``Y`` are correctly empty.
439+
440+
Similarly, annotations from annotated metaclasses leak to unannotated
441+
classes that are instances of the metaclass::
442+
443+
class Meta(type):
444+
a: str
445+
446+
class X(metaclass=Meta):
447+
pass
448+
449+
assert X.__annotations__ == {}, X.__annotations__ # fails: {'a': <class 'str'>}
450+
451+
The reason for these behaviors is that if the metaclass contains an
452+
``__annotations__`` entry in its class dictionary, this prevents
453+
instances of the metaclass from using the ``__annotations__`` data descriptor
454+
on the base :py:class:`type` class. In the first case, accessing ``Meta.__annotations__``
455+
sets ``Meta.__dict__["__annotations__"] = {}`` as a side effect. Then, looking
456+
up the ``__annotations__`` attribute on ``Y`` first sees the metaclass attribute,
457+
but skips it because it is a data descriptor. Next, it looks in the class dictionaries
458+
of the classes in its method resolution order (MRO), finds ``X.__annotations__``,
459+
and returns it. In the second example, there are no annotations
460+
anywhere in the MRO, so ``type.__getattribute__`` falls back to
461+
returning the metaclass attribute.
462+
463+
Metaclass behavior with PEP 649
464+
-------------------------------
465+
466+
With :pep:`649`, the behavior of accessing the ``.__annotations__`` attribute
467+
on classes when metaclasses are involved becomes even more erratic, because now
468+
``__annotations__`` is only lazily added to the class dictionary even for classes
469+
with annotations. The new ``__annotate__`` attribute is also lazily created
470+
on classes without annotations, which causes further misbehaviors when
471+
metaclasses are involved.
472+
473+
The cause of these problems is that we set the ``__annotate__`` and ``__annotations__``
474+
class dictionary entries only under some circumstances, and rely on descriptors
475+
defined on :py:class:`type` to fill them in if they are not set. When normal
476+
attribute lookup is used, this approach breaks down in the presence of
477+
metaclasses, because entries in the metaclass's own class dictionary can render
478+
the descriptors invisible.
479+
480+
While we considered several approaches that would allow ``cls.__annotations__``
481+
and ``cls.__annotate__`` to work reliably when ``cls`` is a type with a custom
482+
metaclass, any such approach would expose significant complexity to advanced users.
483+
Instead, we recommend a simpler approach that confines the complexity to the
484+
``annotationlib`` module: in ``annotationlib.get_annotations``, we bypass normal
485+
attribute lookup by using the ``type.__annotations__`` descriptor directly.
486+
487+
Specification
488+
-------------
489+
490+
Users should always use ``annotationlib.get_annotations`` to access the
491+
annotations of a class object, and ``annotationlib.get_annotate_function``
492+
to access the ``__annotate__`` function. These functions will return only
493+
the class's own annotations, even when metaclasses are involved.
494+
495+
The behavior of accessing the ``__annotations__`` and ``__annotate__``
496+
attributes on classes with a metaclass other than ``builtins.type`` is
497+
unspecified. The documentation should warn against direct use of these
498+
attributes and recommend using the ``annotationlib`` module instead.
499+
500+
Similarly, the presence of ``__annotations__`` and ``__annotate__`` keys
501+
in the class dictionary is an implementation detail and should not be relied
502+
upon.
503+
504+
Rejected alternatives
505+
---------------------
506+
507+
We considered two broad approaches for dealing with the behavior
508+
of the ``__annotations__`` and ``__annotate__`` entries in classes:
509+
510+
* Ensure that the entry is *always* present in the class dictionary, even if it
511+
is empty or has not yet been evaluated. This means we do not have to rely on
512+
the descriptors defined on :py:class:`type` to fill in the field, and
513+
therefore the metaclass's attributes will not interfere. (Prototype
514+
in `gh-120719 <https://github.com/python/cpython/pull/120719>`__.)
515+
* Ensure that the entry is *never* present in the class dictionary, or at least
516+
never added by logic in the language core. This means that the descriptors
517+
on :py:class:`type` will always be used, without interference from the metaclass.
518+
(Prototype in `gh-120816 <https://github.com/python/cpython/pull/120816>`__.)
519+
520+
Alex Waygood suggested an implementation using the first approach. When a
521+
heap type (such as a class created through the ``class`` statement) is created,
522+
``cls.__dict__["__annotations__"]`` is set to a special descriptor.
523+
On ``__get__``, the descriptor evaluates the annotations by calling ``__annotate__``
524+
and returning the result. The annotations dictionary is cached within the
525+
descriptor instance. The descriptor also behaves like a mapping,
526+
so that code that uses ``cls.__dict__["__annotations__"]`` will still usually
527+
work: treating the object as a mapping will evaluate the annotations and behave
528+
as if the descriptor itself was the annotations dictionary. (Code that assumes
529+
that ``cls.__dict__["__annotations__"]`` is specifically an instance of ``dict``
530+
may break, however.)
531+
532+
This approach is also straightforward to implement for ``__annotate__``: this
533+
attribute is already always set for classes with annotations, and we can set
534+
it explicitly to ``None`` for classes without annotations.
535+
536+
While this approach would fix the known edge cases with metaclasses, it
537+
introduces significant complexity to all classes, including a new built-in type
538+
(for the annotations descriptor) with unusual behavior.
539+
540+
The alternative approach would be to never set ``__dict__["__annotations__"]``
541+
and use some other storage to store the cached annotations. This behavior
542+
change would have to apply even to classes defined under
543+
``from __future__ import annotations``, because otherwise there could be buggy
544+
behavior if a class is defined without ``from __future__ import annotations``
545+
but its metaclass does have the future enabled. As :pep:`649` previously noted,
546+
removing ``__annotations__`` from class dictionaries also has backwards compatibility
547+
implications: ``cls.__dict__.get("__annotations__")`` is a common idiom to
548+
retrieve annotations.
549+
550+
This approach would also mean that accessing ``.__annotations__`` on an instance
551+
of an annotated class no longer works. While this behavior is not documented,
552+
it is a long-standing feature of Python and is relied upon by some users.
553+
393554
Remove code flag for marking ``__annotate__`` functions
394555
=======================================================
395556

@@ -695,7 +856,9 @@ Acknowledgments
695856
First of all, I thank Larry Hastings for writing :pep:`649`. This PEP modifies some of his
696857
initial decisions, but the overall design is still his.
697858

698-
I thank Carl Meyer and Alex Waygood for feedback on early drafts of this PEP.
859+
I thank Carl Meyer and Alex Waygood for feedback on early drafts of this PEP. Alex Waygood,
860+
Alyssa Coghlan, and David Ellis provided insightful feedback and suggestions on the
861+
interaction between metaclasses and ``__annotations__``.
699862

700863
Appendix
701864
========

0 commit comments

Comments
 (0)
0