Repository navigation
singledispatch.register fails for lru_cache decorated functions #132064
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Apr 4, 2025 See here (PR to my own package) for implemented workaround, tested with tox for py39~py314 on ubuntu, macOS and windows (-latest).
Also, i'd say there are at least two things wrong here:
- The error message is very cryptic (i'm still not 100% sure what exactly is wrong)
- Either
lru_cacheis not wrapping the function properly or.registeris not picking up the type hints correctly (maybe a fallback to the old behaviour?)
- addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Apr 4, 2025 @rhettinger Was
singledispatchdesigned to work on LRU-decorated functions?for reference, it worked as far back as i've tested, aka down to 3.9. i do realise it might not have been intended to work though, and so this is not necessarily a feature.
Oh so you mean that it's a 3.14+ issue?
yes! sorry, reading back i was not clear about that 😅 (added an
EDITto the original report to make it clearer.)Reacted by Bénédikt TranAs it uses
get_type_hints(func, format=Format.FORWARDREF), I'm asking first @JelleZijlstra. I can have a look tomorrow thoughfunctools.lru_cachecopies essential attributes from the wrapped function withfunctools.update_wrapper. Its current default tuple of attributes isWRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotate__', '__type_params__'). That tuple was last updated by @JelleZijlstra in 2024. Perhaps it should include__annotations__as well. Also contact @ambv to verify that he expects this to work.Reacted by Bénédikt TranThis is indeed a consequence of the implementation of PEP 649/PEP 749, specifically this section: https://peps.python.org/pep-0749/#wrappers-that-provide-annotations .
It is fixable in
_functoolsmodule.cby adding an entry tolru_cache_getsetlistfor__annotations__, providing both a getter and a setter that mirror those for the__annotations__attribute on functions. The setter should unconditionally set__annotations__in the__dict__; the getter should return__annotations__from the__dict__if available, else try to call__annotate__if it's in the dict, and cache the result. Anyone interested in implementing that?@JelleZijlstra, The lru_cache has a pure python implementation as well. The wrapping of both the pure python version and the C version is done in
functools.pywithupdate_wrapper. So, I don't think a C edit is a complete or correct solution.Can this be fixed for the general case by expanding
WRAPPER_ASSIGNMENTSto include__annotations__? The guidance says, "The annotate and annotations attributes should both be supplied with semantics consistent to those of the wrapped object." So it looks to me like__annotations__should be added back toWRAPPER_ASSIGNMENTS:diff --git a/Lib/functools.py b/Lib/functools.py index 714070c6ac9..489b3e1071c 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -30,7 +30,7 @@ # wrapper functions that can handle naive introspection WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', - '__annotate__', '__type_params__') + '__annotate__', '__annotations__', '__type_params__') WRAPPER_UPDATES = ('__dict__',) def update_wrapper(wrapper, wrapped,
Looking at the
lru_cachewrapping code in pure python, I don't see any part of the current issue that is specific to thelru_cache. AFAICT, this would happen to any wrapped function.No, adding
__annotations__to WRAPPER_ASSIGNMENTS would be incorrect because it would immediately force evaluation of the annotations of any wrapped function.The Python implementation of lru_cache already works on 3.14, so it doesn't need any changes to fix this issue. (To test, take the OP's script, comment out the lines in functools.py that import the C implementation, and run the script.)
Another possibility is for
singledispatch.registerto follow the suggestion in the docs forupdate_wrapper, "To allow access to the original function for introspection and other purposes (e.g. bypassing a caching decorator such as lru_cache(), this function automatically adds a wrapped attribute to the wrapper that refers to the function being wrapped."Presumably,
lru_cacheisn't the only decorator that was broken by the edit toWRAPPER_ASSIGNMENTS.I found a better fix for the original issue reported here: #132195. This makes annotationlib use the
__annotate__function if present, even if__annotations__is not also present. This should help not only@lru_cache, but any other decorator that usesupdate_wrapperon a wrapper that is not a Python function.With that fix, it's still the case though that
lru_cache-decorated functions don't have a direct__annotations__attribute when the C version is used:>>> @functools.lru_cache ... def f(x: int): pass ... >>> f.__annotations__ Traceback (most recent call last): File "<python-input-2>", line 1, in <module> f.__annotations__ AttributeError: 'functools._lru_cache_wrapper' object has no attribute '__annotations__'. Did you mean: '__annotate__'? >>> f.__wrapped__.__annotations__ {'x': <class 'int'>} >>> f.__annotate__(1) {'x': <class 'int'>}This could impact users who (against recommendations) access
__annotations__directly instead of usingannotationlib.get_annotationsor (in previous Python versions)inspect.get_annotations. To fix it, we could add a dedicated__annotations__descriptor to the C version of functools._lru_cache_wrapper. Is that worth it? I'm not sure.- added a commit that references this issue
on Apr 13, 2025
Bug report
Function decorated with both
@singledispatch.registerand@lru_chachefails with a cryptic error message.EDIT: In python 3.14a6. It works in python 3.9~3.13.
Bug description:
python ./singledispatch_mwe.py, butpython ./singledispatch_mwe.py --broken-in-py314produces the following error:In python 3.9~3.13 both of the above work.
CPython versions tested on:
3.9~3.14
Operating systems tested on:
macOS
Linked PRs