From 93206593a9905c8efb4293574193e9d2995f7193 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 27 Oct 2026 14:35:25 -0400 Subject: [PATCH 1/2] Allow unhashable arguments when lru_cache is disabled A zero-size cache calls the original function without constructing a key. Calling lru_cache(0)(len)([]) therefore succeeds, but the wrapper required every argument to be Hashable. Default the wrapper's argument type to Hashable and use object when maxsize is literally zero or False. Preserve that type when copying the wrapper, and make its own receiver positional-only so a wrapped function can receive a keyword named self. Unknown sizes retain the hash check. --- stdlib/@tests/test_cases/check_functools.py | 11 +++++++++++ stdlib/functools.pyi | 11 +++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/stdlib/@tests/test_cases/check_functools.py b/stdlib/@tests/test_cases/check_functools.py index e3b90ab5743f..d3aaf5ca1c4d 100644 --- a/stdlib/@tests/test_cases/check_functools.py +++ b/stdlib/@tests/test_cases/check_functools.py @@ -122,6 +122,17 @@ def check_lru_cache_with_maxsize(x: int) -> int: assert_type(check_lru_cache_with_maxsize.cache_info().misses, int) +def check_cache_keys() -> None: + def size(*, self: list[int]) -> int: + return len(self) + + disabled = lru_cache(maxsize=0)(size) + assert_type(disabled(self=[]), int) + assert_type(lru_cache(False, typed=True)(size)(self=[]), int) + cache(size)(self=[]) # type: ignore[arg-type] + lru_cache(1)(size)(self=[]) # type: ignore[arg-type] + + # # Tests for @cached_property # diff --git a/stdlib/functools.pyi b/stdlib/functools.pyi index 6c8ab541fe48..4c33caf7a611 100644 --- a/stdlib/functools.pyi +++ b/stdlib/functools.pyi @@ -46,6 +46,7 @@ _PWrapper = ParamSpec("_PWrapper") _RWrapper = TypeVar("_RWrapper") _WrapperSelfT = TypeVar("_WrapperSelfT") _PBound = ParamSpec("_PBound") +_CacheArgT_contra = TypeVar("_CacheArgT_contra", contravariant=True, default=Hashable) if sys.version_info >= (3, 14): @overload @@ -69,19 +70,21 @@ class _CacheParameters(TypedDict): typed: bool @final -class _lru_cache_wrapper(Generic[_T_co]): +class _lru_cache_wrapper(Generic[_T_co, _CacheArgT_contra]): __wrapped__: Callable[..., _T_co] - def __call__(self, *args: Hashable, **kwargs: Hashable) -> _T_co: ... + def __call__(self, /, *args: _CacheArgT_contra, **kwargs: _CacheArgT_contra) -> _T_co: ... def cache_info(self) -> _CacheInfo: ... def cache_clear(self) -> None: ... def cache_parameters(self) -> _CacheParameters: ... - def __copy__(self) -> _lru_cache_wrapper[_T_co]: ... - def __deepcopy__(self, memo: Any, /) -> _lru_cache_wrapper[_T_co]: ... + def __copy__(self) -> Self: ... + def __deepcopy__(self, memo: Any, /) -> Self: ... # as with ``Callable``, we'll assume that these attributes exist __name__: str __qualname__: str +@overload +def lru_cache(maxsize: Literal[0, False], typed: bool = False) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T, object]]: ... @overload def lru_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T]]: ... @overload From e70bcace99c919193551c70b00523e136cac00cf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:40:24 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/functools.pyi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stdlib/functools.pyi b/stdlib/functools.pyi index 4c33caf7a611..42041befd999 100644 --- a/stdlib/functools.pyi +++ b/stdlib/functools.pyi @@ -84,7 +84,9 @@ class _lru_cache_wrapper(Generic[_T_co, _CacheArgT_contra]): __qualname__: str @overload -def lru_cache(maxsize: Literal[0, False], typed: bool = False) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T, object]]: ... +def lru_cache( + maxsize: Literal[0, False], typed: bool = False +) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T, object]]: ... @overload def lru_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T]]: ... @overload