Title: annotationlib: `ref.evaluate(format=Format.FORWARDREF)` returns a ForwardRef with a copied `__globals__` that no longer updates · Issue #137969 · python/cpython · GitHub
Open Graph Title: annotationlib: `ref.evaluate(format=Format.FORWARDREF)` returns a ForwardRef with a copied `__globals__` that no longer updates · Issue #137969 · python/cpython
X Title: annotationlib: `ref.evaluate(format=Format.FORWARDREF)` returns a ForwardRef with a copied `__globals__` that no longer updates · Issue #137969 · python/cpython
Description: Bug report Bug description: This can happen internally in get_annotations and means subsequent attempts to evaluate it will fail even if the names have since been defined. Underlying logic: from annotationlib import get_annotations, Form...
Open Graph Description: Bug report Bug description: This can happen internally in get_annotations and means subsequent attempts to evaluate it will fail even if the names have since been defined. Underlying logic: from an...
X Description: Bug report Bug description: This can happen internally in get_annotations and means subsequent attempts to evaluate it will fail even if the names have since been defined. Underlying logic: from an...
Opengraph URL: https://github.com/python/cpython/issues/137969
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"annotationlib: `ref.evaluate(format=Format.FORWARDREF)` returns a ForwardRef with a copied `__globals__` that no longer updates","articleBody":"# Bug report\n\n### Bug description:\n\nThis can happen internally in `get_annotations` and means subsequent attempts to evaluate it will fail even if the names have since been defined.\n\nUnderlying logic:\n```python\nfrom annotationlib import get_annotations, Format\n\nclass Demo:\n x: Sequence[undefined]\n\nannos = get_annotations(Demo, format=Format.FORWARDREF)\n\nx_anno = annos['x']\n\n# Try to evaluate the reference, but just give a forwardref if it fails\nx_repeat_anno = x_anno.evaluate(format=Format.FORWARDREF)\n\n# The resulting reference no longer shares the globals namespace with the annotate function\nprint(f\"{x_anno.__globals__ is Demo.__annotate__.__globals__ = }\") # True\nprint(f\"{x_repeat_anno.__globals__ is Demo.__annotate__.__globals__ = }\") # False\n\n# Define the previously undefined attributes\nfrom collections.abc import Sequence\nundefined = str\n\n# This means evaluation fails in the second case\nprint(f\"{x_anno.evaluate() = }\") # collections.abc.Sequence[str]\nprint(f\"{x_repeat_anno.evaluate() = }\") # NameError\n```\n\nThis `evaluate` call happens internally if `get_annotations` has to rely on the fallback behaviour for an unexpected exception, such as an `AttributeError`:\n\n```python\nfrom annotationlib import get_annotations, Format\nimport typing\n\nclass Works:\n a: Sequence[undefined]\n b: unknowable\n\n# Intentionally set up an annotation that will raise AttributeError on evaluation\nclass Fails:\n a: Sequence[undefined]\n b: typing.doesnotexist\n\na_works = get_annotations(Works, format=Format.FORWARDREF)['a']\na_fails = get_annotations(Fails, format=Format.FORWARDREF)['a']\n\n# Realise the references\nfrom collections.abc import Sequence\nundefined = str\n\nprint(f\"{a_works.evaluate() = }\") # collections.abc.Sequence[str]\nprint(f\"{a_fails.evaluate() = }\") # NameError\n```\n\nThis appears to be caused by the creation of a new globals dict here:\nhttps://github.com/python/cpython/blob/e39255e76d4b6755a44f6d4e63180136c778d2a5/Lib/annotationlib.py#L164-L167\n\nCommenting this out makes these examples succeed, but obviously breaks type parameters.\n\n### CPython versions tested on:\n\nCPython main branch, 3.14\n\n### Operating systems tested on:\n\n_No response_\n\n\u003c!-- gh-linked-prs --\u003e\n### Linked PRs\n* gh-138075\n* gh-140929\n* gh-140930\n* gh-140931\n* gh-140974\n* gh-141527\n\u003c!-- /gh-linked-prs --\u003e\n","author":{"url":"https://github.com/DavidCEllis","@type":"Person","name":"DavidCEllis"},"datePublished":"2025-08-19T19:45:53.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":8},"url":"https://github.com/137969/cpython/issues/137969"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:2bd840ef-d035-57ae-cf89-0213d2b4395e |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | E338:1672C1:31784C:40DB47:696ADC7A |
| html-safe-nonce | c8fda41b291e790e5418f8c83abd8dab284fc1ed3c12f4591a0c45943e47ce6a |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFMzM4OjE2NzJDMTozMTc4NEM6NDBEQjQ3OjY5NkFEQzdBIiwidmlzaXRvcl9pZCI6IjI0NzYzMTc3MTQ3Mjg4MDM0NTAiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | dea69c48d10f0fd7386676086ccd223c436c4ca7a561ed0e66dfbd68c3aa5cd5 |
| hovercard-subject-tag | issue:3335489085 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/python/cpython/137969/issue_layout |
| twitter:image | https://opengraph.githubassets.com/8a0dc4c8f11c81ecda592bce008280df70a046e1406788b9abe5c62ab8f78ca2/python/cpython/issues/137969 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/8a0dc4c8f11c81ecda592bce008280df70a046e1406788b9abe5c62ab8f78ca2/python/cpython/issues/137969 |
| og:image:alt | Bug report Bug description: This can happen internally in get_annotations and means subsequent attempts to evaluate it will fail even if the names have since been defined. Underlying logic: from an... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | DavidCEllis |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5f99f7c1d70f01da5b93e5ca90303359738944d8ab470e396496262c66e60b8d |
| turbo-cache-control | no-preview |
| go-import | github.com/python/cpython git https://github.com/python/cpython.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 81598961 |
| octolytics-dimension-repository_nwo | python/cpython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 81598961 |
| octolytics-dimension-repository_network_root_nwo | python/cpython |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 524a93f2c1f36522a3b4be4c110467ee4172245d |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width