bpo-36772 Allow lru_cache to be used as decorator without making a function call (GH-13048)
diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst
index 16a779f..8b8b1f8 100644
--- a/Doc/library/functools.rst
+++ b/Doc/library/functools.rst
@@ -76,7 +76,8 @@
.. versionadded:: 3.2
-.. decorator:: lru_cache(maxsize=128, typed=False)
+.. decorator:: lru_cache(user_function)
+ lru_cache(maxsize=128, typed=False)
Decorator to wrap a function with a memoizing callable that saves up to the
*maxsize* most recent calls. It can save time when an expensive or I/O bound
@@ -90,6 +91,15 @@
differ in their keyword argument order and may have two separate cache
entries.
+ If *user_function* is specified, it must be a callable. This allows the
+ *lru_cache* decorator to be applied directly to a user function, leaving
+ the *maxsize* at its default value of 128::
+
+ @lru_cache
+ def count_vowels(sentence):
+ sentence = sentence.casefold()
+ return sum(sentence.count(vowel) for vowel in 'aeiou')
+
If *maxsize* is set to ``None``, the LRU feature is disabled and the cache can
grow without bound. The LRU feature performs best when *maxsize* is a
power-of-two.
@@ -165,6 +175,9 @@
.. versionchanged:: 3.3
Added the *typed* option.
+ .. versionchanged:: 3.8
+ Added the *user_function* option.
+
.. decorator:: total_ordering
Given a class defining one or more rich comparison ordering methods, this
diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index a94aba6..1180469 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -291,6 +291,23 @@
DLL) and paths added by :func:`~os.add_dll_directory`.
+functools
+---------
+
+:func:`functools.lru_cache` can now be used as a straight decorator rather
+than as a function returning a decorator. So both of these are now supported::
+
+ @lru_cache
+ def f(x):
+ ...
+
+ @lru_cache(maxsize=256)
+ def f(x):
+ ...
+
+(Contributed by Raymond Hettinger in :issue:`36772`.)
+
+
datetime
--------
diff --git a/Lib/functools.py b/Lib/functools.py
index c863341..30964a6 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -518,14 +518,18 @@
# The internals of the lru_cache are encapsulated for thread safety and
# to allow the implementation to change (including a possible C version).
- # Early detection of an erroneous call to @lru_cache without any arguments
- # resulting in the inner function being passed to maxsize instead of an
- # integer or None. Negative maxsize is treated as 0.
if isinstance(maxsize, int):
+ # Negative maxsize is treated as 0
if maxsize < 0:
maxsize = 0
+ elif callable(maxsize) and isinstance(typed, bool):
+ # The user_function was passed in directly via the maxsize argument
+ user_function, maxsize = maxsize, 128
+ wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
+ return update_wrapper(wrapper, user_function)
elif maxsize is not None:
- raise TypeError('Expected maxsize to be an integer or None')
+ raise TypeError(
+ 'Expected first argument to be an integer, a callable, or None')
def decorating_function(user_function):
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index b89d779..8fee1c6 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -1251,6 +1251,18 @@
self.assertEqual(misses, 4)
self.assertEqual(currsize, 2)
+ def test_lru_no_args(self):
+ @self.module.lru_cache
+ def square(x):
+ return x ** 2
+
+ self.assertEqual(list(map(square, [10, 20, 10])),
+ [100, 400, 100])
+ self.assertEqual(square.cache_info().hits, 1)
+ self.assertEqual(square.cache_info().misses, 2)
+ self.assertEqual(square.cache_info().maxsize, 128)
+ self.assertEqual(square.cache_info().currsize, 2)
+
def test_lru_bug_35780(self):
# C version of the lru_cache was not checking to see if
# the user function call has already modified the cache
@@ -1582,13 +1594,6 @@
self.assertEqual(test_func(DoubleEq(2)), # Trigger a re-entrant __eq__ call
DoubleEq(2)) # Verify the correct return value
- def test_early_detection_of_bad_call(self):
- # Issue #22184
- with self.assertRaises(TypeError):
- @functools.lru_cache
- def f():
- pass
-
def test_lru_method(self):
class X(int):
f_cnt = 0
diff --git a/Misc/NEWS.d/next/Library/2019-05-01-20-41-53.bpo-36772.fV2K0F.rst b/Misc/NEWS.d/next/Library/2019-05-01-20-41-53.bpo-36772.fV2K0F.rst
new file mode 100644
index 0000000..00b8a68
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-01-20-41-53.bpo-36772.fV2K0F.rst
@@ -0,0 +1,2 @@
+functools.lru_cache() can now be used as a straight decorator in
+addition to its existing usage as a function that returns a decorator.