From c403793a0e4e5e5e4112dd040e2cebe3df0f0902 Mon Sep 17 00:00:00 2001 From: CF Bolz-Tereick Date: Mon, 19 Jan 2026 12:43:30 +0100 Subject: [PATCH 1/4] gh-144030: check that argument is callable in py version of lru_cache --- Lib/functools.py | 3 +++ Lib/test/test_functools.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/Lib/functools.py b/Lib/functools.py index 075418b1605a48..59fc2a8fbf6219 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -602,6 +602,9 @@ def decorating_function(user_function): return decorating_function def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo): + if not callable(user_function): + raise TypeError("the first argument must be callable") + # Constants shared by all lru cache instances: sentinel = object() # unique object used to signal cache misses make_key = _make_key # build a key from the function arguments diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 94b469397139c7..db30f58ed8e3c1 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2157,6 +2157,11 @@ def fib(n): with self.assertRaises(RecursionError): fib(support.exceeds_recursion_limit()) + def test_lru_checks_arg_is_callable(self): + with self.assertRaises(TypeError) as te: + self.module.lru_cache(1)('hello') + self.assertIn("the first argument must be callable", str(te.exception)) + @py_functools.lru_cache() def py_cached_func(x, y): From 3d579eaa3960ea490d5ac29ce458f06c772e91f4 Mon Sep 17 00:00:00 2001 From: CF Bolz-Tereick Date: Mon, 19 Jan 2026 12:49:02 +0100 Subject: [PATCH 2/4] add news entry --- .../Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst diff --git a/Misc/NEWS.d/next/Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst b/Misc/NEWS.d/next/Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst new file mode 100644 index 00000000000000..33e8f4ff478acd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst @@ -0,0 +1,3 @@ +The Python implementation of :func:`functools.lru_cache` differed from the +default C implementation in that it did not check that its argument is +callable. This discrepancy is now fixed and both raise a :class:`TypeError`. From d84cab3f7e722c60695e6618b160a726213e65d3 Mon Sep 17 00:00:00 2001 From: CF Bolz-Tereick Date: Mon, 19 Jan 2026 15:46:01 +0100 Subject: [PATCH 3/4] Update Lib/test/test_functools.py Co-authored-by: sobolevn --- Lib/test/test_functools.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index db30f58ed8e3c1..3801a82a610891 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2158,9 +2158,11 @@ def fib(n): fib(support.exceeds_recursion_limit()) def test_lru_checks_arg_is_callable(self): - with self.assertRaises(TypeError) as te: + with self.assertRaisesRegex( + TypeError, + "the first argument must be callable", + ): self.module.lru_cache(1)('hello') - self.assertIn("the first argument must be callable", str(te.exception)) @py_functools.lru_cache() From 914ff170d718bb94e683fa49bbc8e050b95b3eac Mon Sep 17 00:00:00 2001 From: CF Bolz-Tereick Date: Tue, 20 Jan 2026 11:17:15 +0100 Subject: [PATCH 4/4] Update Misc/NEWS.d/next/Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst Co-authored-by: AN Long --- .../next/Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst b/Misc/NEWS.d/next/Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst index 33e8f4ff478acd..ef3c02925405b8 100644 --- a/Misc/NEWS.d/next/Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst +++ b/Misc/NEWS.d/next/Library/2026-01-19-12-48-59.gh-issue-144030.7OK_gB.rst @@ -1,3 +1,3 @@ The Python implementation of :func:`functools.lru_cache` differed from the default C implementation in that it did not check that its argument is -callable. This discrepancy is now fixed and both raise a :class:`TypeError`. +callable. This discrepancy is now fixed and both raise a :exc:`TypeError`.