Fix alru_cache crash with deferred annotations on Python 3.14#781
Fix alru_cache crash with deferred annotations on Python 3.14#781rodrigobnogueira wants to merge 6 commits into
Conversation
PEP 649 makes annotations lazy; copying fn.__annotations__ at decoration time forces their evaluation and raises NameError for names defined after the decorated function. Copy the lazy __annotate__ function the way functools.update_wrapper does on 3.14, keeping the __annotations__ copy as the fallback for older versions, in both the function and the bound-method wrappers.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #781 +/- ##
==========================================
+ Coverage 97.78% 98.39% +0.60%
==========================================
Files 16 17 +1
Lines 1176 1246 +70
Branches 61 63 +2
==========================================
+ Hits 1150 1226 +76
+ Misses 23 17 -6
Partials 3 3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merging this PR will not alter performance
Comparing Footnotes
|
Covers the wrapper bodies the parity test previously only inspected, and adds a partial-bound-as-method test so the attribute-copy fallback that copies neither annotation attribute is exercised.
| # names that are not defined yet. | ||
| try: | ||
| self.__annotations__ = fn.__annotations__ | ||
| self.__annotate__ = fn.__annotate__ # type: ignore[attr-defined] |
There was a problem hiding this comment.
@rodrigobnogueira I would recommend using sys.version_info as a faster workaround for this problem as this is 3.14+ related, this way this part of the code doesn't trigger a performance regression.
if sys.version_info >= (3, 14):
self.__annotate__ = fn.__annotate__ # type: ignore[attr-defined]There was a problem hiding this comment.
Done in 73906f3. Both copy sites now branch on sys.version_info >= (3, 14); older interpreters do the plain __annotations__ copy with no try/except on a missing __annotate__. The narrow try/except stays inside the 3.14 branch for wrapped partials, which the partial test covers. The type: ignore[attr-defined] became unused under the gate and was removed. Suite green on 3.12 and 3.14, mypy clean on both.
Older interpreters take the plain __annotations__ copy directly instead of raising and catching an AttributeError on every decoration. Suggested by Vizonex in review.
Vizonex
left a comment
There was a problem hiding this comment.
I like this change. Thank you for this now there shouldn't be a performance regressions with older versions of python.
| # Python 3.14+ (PEP 649): prefer the lazy __annotate__ function, | ||
| # see the matching logic in _LRUCacheWrapper.__init__. |
There was a problem hiding this comment.
Should really make both comments concise like this. I'd also drop the prefix so there's less maintenance. We can see the version check below.
There was a problem hiding this comment.
Done in 26f7eda — both comments trimmed, prefix dropped.
What do these changes do?
Fix
alru_cachecrashing at decoration time on Python 3.14 when an annotation refers to a name defined after the decorated function. PEP 649 makes annotations lazy, and copyingfn.__annotations__eagerly forces their evaluation;functools.update_wrappercopies the lazy__annotate__function on 3.14 instead, so both wrapper classes now do the same, keeping the__annotations__copy as the fallback for 3.13 and older. Laziness now matchesfunctools.lru_cache:inspect.get_annotations()resolves once the name exists and raisesNameErroronly on access when it never does.Are there changes in behavior for the user?
Only on Python 3.14: the wrapper no longer exposes an eagerly evaluated
__annotations__attribute; annotations are reachable throughinspect.get_annotations()andtyping.get_type_hints(), matchingfunctools.lru_cachethere. No changes on 3.13 and older.Is it a substantial burden for the maintainers to support this?
No.
Related issue number
Fixes #773
Checklist