Skip to content

Metaclasses with type annotated attributes do not add __annotations__ = {} to derived classes. #149208

Open

Description

@matthewwardrop

Bug report

Bug description:

This can cause odd behaviour for subclasses which then propagate their parent's annotations rather than their own.

class MyType(type):
    METACLASS_VAR: int = 1

class A(metaclass=MyType):
    value: str = "HI"

class B(A):
    pass

B.__annotations__  # {"value": str} instead of {}; but if you remove the type annotation in `MyType`, you get {}

This issue only seems to affect versions prior to 3.14

CPython versions tested on:

3.10, 3.13, 3.14

Operating systems tested on:

Linux

Activity

  1. JelleZijlstra commented on May 1, 2026

    @JelleZijlstra
    Member

    This is discussed in https://peps.python.org/pep-0749/#pre-existing-bugs and fixed in 3.14 (unless you use from __future__ import annotations).

  2. matthewwardrop commented on May 1, 2026

    @matthewwardrop
    Author

    Oh... just noted that as I did a quick test on Python 3.14. I tried search for this in Google, but didn't find it (probably had the wrong keywords). Thanks for linking me on @JelleZijlstra . Feel free to close this issue if this is considered a non-issue going forward.

  3. DavidCEllis commented on May 1, 2026

    @DavidCEllis
    Contributor

    It is somewhat of an obscure place for this to be documented but it is also probably a fairly rare thing to come across? I think technically this was supposed to be fixed in 3.10 where it was fixed for classes without the metaclass.

    Whether or not this is considered worth fixing for __future__ annotations or as a bugfix on 3.13, perhaps it may be worth putting something in the annotations best practices page about this? Currently it indicates that in 3.10 and newer you can use getattr if you can't use inspect.get_annotations which would lead you to this bug.

  4. JelleZijlstra commented on May 1, 2026

    @JelleZijlstra
    Member

    I wouldn't mind documenting this in the best practices doc.

    We could also fix this (in 3.13, and in 3.14+ when from __future__ import annotations is involved), but I suspect any fix will also break some other ways of using annotations and it's better left alone.

  5. added
    docsDocumentation in the Doc dir
    and removed
    type-bugAn unexpected behavior, bug, or error
    on May 1, 2026
  6. Taeknology commented on Jul 31, 2026

    @Taeknology
    Contributor

    I took a look at this issue and noticed that the custom-metaclass behavior already appears to be documented in the __annotations__ quirks section of the Annotations Best Practices HOWTO.

    That section links to the PEP 749 metaclass examples and recommends using annotationlib.get_annotations() on Python 3.14+, inspect.get_annotations() on Python 3.10+, or reading the class's own __dict__ on earlier versions. The paragraph was added in gh-119180 / PR #122235.

    On current main, I can still reproduce the direct-access behavior with from __future__ import annotations:

    from __future__ import annotations
    from annotationlib import get_annotations
    
    class Meta(type):
        value: int
    
    class Base(metaclass=Meta):
        field: str
    
    class Derived(Base):
        pass
    
    print(Derived.__annotations__)   # {'field': 'str'}
    print(get_annotations(Derived))  # {}

    Is the existing HOWTO guidance sufficient to resolve this issue, or would it be useful to add a more prominent caveat near the earlier statement that direct __annotations__ access works on classes in Python 3.10 and newer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions