Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
zzzeek opened this issue May 9, 2025 · 6 comments
Assignees
Labels
3.14 bugs and security fixes stdlib Python modules in the Lib dir topic-typing type-bug An unexpected behavior, bug, or error

Comments

@zzzeek
Copy link

zzzeek commented May 9, 2025

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

@zzzeek
Copy link
Author

zzzeek commented May 9, 2025

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!

@JelleZijlstra JelleZijlstra 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 May 9, 2025
@JelleZijlstra
Copy link
Member

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

@zzzeek
Copy link
Author

zzzeek commented May 9, 2025

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

@picnixz picnixz added stdlib Python modules in the Lib dir 3.14 bugs and security fixes topic-typing labels May 9, 2025
@JelleZijlstra
Copy link
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.

@zzzeek
Copy link
Author

zzzeek commented May 9, 2025

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.

@JelleZijlstra
Copy link
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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.14 bugs and security fixes stdlib Python modules in the Lib dir topic-typing type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants