Skip to content

assigning to __annotations__ has no effect in 3.14.0b1 under from __future__ import annotations #133778

Closed

Description

@zzzeek

Bug report

Bug description:

from __future__ import annotations
import annotationlib

class A:
    a: int

A.__annotations__ = {"a": str}


print(A.__annotations__)
print(annotationlib.get_annotations(A))

this works in 3.14.0a7, has no effect in 3.14.0b1. there seems to be a new attribute __annotations_cache__ that the assignment goes into but it isn't returned from __annotations__:

# 3.14.0a7
$ ~/.venv314a7/bin/python test4.py 
{'a': <class 'str'>}
{'a': <class 'str'>}

# 3.14.0b1
$ ~/.venv314/bin/python test4.py 
{'a': 'int'}
{'a': 'int'}

If there's a way to get annotationlib.get_annotations() to pick up the change I can use that as a workaround

cc @JelleZijlstra

CPython versions tested on:

3.14

Operating systems tested on:

No response

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on May 9, 2025
  2. self-assigned this
    on May 9, 2025
  3. zzzeek commented on May 9, 2025

    @zzzeek
    Author

    if this is a supported pattern (which I wasn't sure if it was or not) then I assume some tests will be added, thanks!

  4. changed the title [-]assigning to __annotations__ has no effect in 3.14.0b1[/-] [+]assigning to __annotations__ has no effect in 3.14.0b1 under from __future__ import annotations[/+] on May 9, 2025
  5. JelleZijlstra commented on May 9, 2025

    @JelleZijlstra
    Member

    Yes it is, we are not testing the from __future__ import annotations version well enough.

  6. zzzeek commented on May 9, 2025

    @zzzeek
    Author

    thanks! Here's hoping for a much more boring 3.14.0b2 release :)

  7. added
    stdlibStandard Library Python modules in the Lib/ directory
    3.14bugs and security fixes
    on May 9, 2025
  8. JelleZijlstra commented on May 9, 2025

    @JelleZijlstra
    Member

    Yes! I'm hoping to get all the fixes in over the weekend, after that I can try to run the SQLAlchemy test suite to make sure it's all fixed.

  9. zzzeek commented on May 9, 2025

    @zzzeek
    Author

    thanks. It's not that SQLAlchemy is doing everything great, or correctly, or what it said is allowed on the label, but at least it's a way to trap changes in behavior which you can then look at on a case by case basis to see if these changes were intended.

  10. JelleZijlstra commented on May 10, 2025

    @JelleZijlstra
    Member

    I'd like to ask for other opinions here (maybe @carljm @larryhastings) so I'm going to try to explain the situation.

    Currently, classes with and without from __future__ import annotations store their annotations differently in the class namespace:

    • Without the future import, there is an __annotate_func__ key with the annotate function. If .__annotations__ is accessed, the annotations are cached in the __annotations_cache__ key.
    • With the future import, there is an __annotations__ key that holds the annotations.

    When reading annotations (type_get_annotations), we first read the __annotations__ key in the class dictionary, then __annotations_cache__, then we get the annotate function.

    The background for this is described in https://peps.python.org/pep-0749/#annotations-and-metaclasses.

    When you set annotations on a type, type_set_annotations in typeobject.c is invoked. Currently, it always assigns to the __annotations_cache__ key. That leads to the bug described here where assigning to __annotations__ appears to do nothing for classes defined with the future import, because in the getter we always try __annotations__ before __annotations_cache__.

    I'm considering a few options for how to solve this:

    1. Make type_set_annotations always assign to the __annotations__ key. This has the tradeoff that you can now induce buggy behavior even without the future import: if you assign to .__annotations__ of a metaclass, then access .__annotations__ on an instance of that metaclass, you'll get the metaclass's annotations. I want that sort of misbehavior to be impossible when you don't use the future import, so in the long term we can say that accessing .__annotations__ on a class is safe.
    2. Make type_set_annotations check if __annotations__ is already present in the class namespace. If so, replace it; otherwise replace __annotations_cache__. This avoids the bug above, but creates another subtle misbehavior. Consider a metaclass with the future enabled, and an instance of that metaclass defined without the future. Now if you assign to .__annotations__ of the class, on subsequent reads of .__annotations__ you'll get the metaclass's annotations, not the annotations you just set. However, you'll also get the metaclass's annotations before you do this; get_annotations() will correctly return the annotations you just set; and this bug only appears in the presence of the (now deprecated) future import.
    3. We can also do something more invasive, like storing the annotations somewhere else on classes defined with the future. However, I'd like to avoid this, since I prefer to keep behavior mostly the same when the future import is enabled.

    So I'd like to go with option (2).

  11. added a commit that references this issue on May 10, 2025
  12. JelleZijlstra commented on May 11, 2025

    @JelleZijlstra
    Member

    I ran the sqlalchemy test suite on a build of 3.14 with my three fixes #133795 #133794 #133772 applied.

    On sqlalchemy main I get one failure (among >13k tests, impressive): FAILED test/typing/test_overloads.py::OverloadTest::test_typed_dicts[Union-expected1-_exclusions_01] - AssertionError: Unexpected success for 'test_typed_dicts' (Fails as of python 3.14.0b1), which I guess you marked as XFAIL, so it's good that it passes now. Same on the rel_2_0 branch.

    I also tried to run the other tests in typing-extensions's third-party test suite. cattrs failed to install due to some issue I don't understand; a couple of others had failures that I fixed or opened issues about; but nothing else indicative of bugs in CPython.

  13. zzzeek commented on May 12, 2025

    @zzzeek
    Author

    that's great - the test suite that actually tests the assignment of __annotations__ issue however is not committed, it can be seen in this gerrit review: https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5858

    you can checkout this gerrit review with commands such as:

    git fetch https://gerrit.sqlalchemy.org/sqlalchemy/sqlalchemy refs/changes/58/5858/2 && git checkout FETCH_HEAD
    
    

    that suite includes a new file test/orm/declarative/test_dc_transforms_future_anno_sync.py which is a copy of an existing suite that adds the from __future__ import annotations directive at the top. that's the suite that fails with the current issue. if you install SQLAlchemy dependencies as well as pytest you can run it directly as pytest test/orm/declarative/test_dc_transforms_future_anno_sync.py.

  14. JelleZijlstra commented on May 12, 2025

    @JelleZijlstra
    Member

    Thanks, that test suite also passes on my branch (though a bunch of things are skipped with "doesn't work for future annotations mode yet"; not sure if that's due to missing work on your side or due to CPython bugs).

  15. zzzeek commented on May 12, 2025

    @zzzeek
    Author

    that's great, those skipped tests are things on my end that I'd have to update to work with future (I'm comparing the contents of __annotations__ to known quantities, so some of these things become ForwardRefs etc), if the suite passes then we're all good. thanks very much!

  16. added a commit that references this issue on May 25, 2025
  17. added a commit that references this issue on May 25, 2025
  18. added a commit that references this issue on May 25, 2025
  19. added a commit that references this issue on Jul 12, 2025
  20. added a commit that references this issue on Aug 4, 2025
  21. added a commit that references this issue on Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

3.14bugs and security fixesstdlibStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or error

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions