bpo-36772 Allow lru_cache to be used as decorator without making a function call (GH-13048)

This commit is contained in:
Raymond Hettinger 2019-05-26 11:27:35 -07:00 committed by GitHub
parent aaf47caf35
commit b821868e6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 53 additions and 12 deletions

View 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

View 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
--------

View 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)

View file

@ -1251,6 +1251,18 @@ def f(x):
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 @@ def __eq__(self, other):
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

View file

@ -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.