]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-36772 Allow lru_cache to be used as decorator without making a function call...
authorRaymond Hettinger <rhettinger@users.noreply.github.com>
Sun, 26 May 2019 18:27:35 +0000 (11:27 -0700)
committerGitHub <noreply@github.com>
Sun, 26 May 2019 18:27:35 +0000 (11:27 -0700)
Doc/library/functools.rst
Doc/whatsnew/3.8.rst
Lib/functools.py
Lib/test/test_functools.py
Misc/NEWS.d/next/Library/2019-05-01-20-41-53.bpo-36772.fV2K0F.rst [new file with mode: 0644]

index 16a779fa836858007ff7cff08346ade8f6da90af..8b8b1f80a622e757966af71fba4a69468188a341 100644 (file)
@@ -76,7 +76,8 @@ The :mod:`functools` module defines the following functions:
    .. 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 @@ The :mod:`functools` module defines the following functions:
    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 @@ The :mod:`functools` module defines the following functions:
    .. 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
index a94aba6b2c987f60c1735f98fd409bb6604f8f06..1180469ff28f656defc75821de1c0c2a673ea0e7 100644 (file)
@@ -291,6 +291,23 @@ where the DLL is stored (if a full or partial path is used to load the initial
 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
 --------
 
index c863341eec5f21575685cf825bfd51de2b9bbd4c..30964a6fe3d83a00d420259f9f43b77a432c9804 100644 (file)
@@ -518,14 +518,18 @@ def lru_cache(maxsize=128, typed=False):
     # 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)
index b89d77967a0df2f57514099fe23a652a00ad8e0a..8fee1c6afdd450e48c613f5cafbc4e2853695359 100644 (file)
@@ -1251,6 +1251,18 @@ class TestLRU:
         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 @@ class TestLRU:
         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 (file)
index 0000000..00b8a68
--- /dev/null
@@ -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.