bpo-36772 Allow lru_cache to be used as decorator without making a fu… · python/cpython@b821868

5 files changed

lines changed

Original file line numberDiff line numberDiff line change

@@ -76,7 +76,8 @@ The :mod:`functools` module defines the following functions:

7676

.. versionadded:: 3.2

7777
7878
79-

.. decorator:: lru_cache(maxsize=128, typed=False)

79+

.. decorator:: lru_cache(user_function)

80+

lru_cache(maxsize=128, typed=False)

8081
8182

Decorator to wrap a function with a memoizing callable that saves up to the

8283

*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:

9091

differ in their keyword argument order and may have two separate cache

9192

entries.

9293
94+

If *user_function* is specified, it must be a callable. This allows the

95+

*lru_cache* decorator to be applied directly to a user function, leaving

96+

the *maxsize* at its default value of 128::

97+
98+

@lru_cache

99+

def count_vowels(sentence):

100+

sentence = sentence.casefold()

101+

return sum(sentence.count(vowel) for vowel in 'aeiou')

102+
93103

If *maxsize* is set to ``None``, the LRU feature is disabled and the cache can

94104

grow without bound. The LRU feature performs best when *maxsize* is a

95105

power-of-two.

@@ -165,6 +175,9 @@ The :mod:`functools` module defines the following functions:

165175

.. versionchanged:: 3.3

166176

Added the *typed* option.

167177
178+

.. versionchanged:: 3.8

179+

Added the *user_function* option.

180+
168181

.. decorator:: total_ordering

169182
170183

Given a class defining one or more rich comparison ordering methods, this

Original file line numberDiff line numberDiff line change

@@ -291,6 +291,23 @@ where the DLL is stored (if a full or partial path is used to load the initial

291291

DLL) and paths added by :func:`~os.add_dll_directory`.

292292
293293
294+

functools

295+

---------

296+
297+

:func:`functools.lru_cache` can now be used as a straight decorator rather

298+

than as a function returning a decorator. So both of these are now supported::

299+
300+

@lru_cache

301+

def f(x):

302+

...

303+
304+

@lru_cache(maxsize=256)

305+

def f(x):

306+

...

307+
308+

(Contributed by Raymond Hettinger in :issue:`36772`.)

309+
310+
294311

datetime

295312

--------

296313
Original file line numberDiff line numberDiff line change

@@ -518,14 +518,18 @@ def lru_cache(maxsize=128, typed=False):

518518

# The internals of the lru_cache are encapsulated for thread safety and

519519

# to allow the implementation to change (including a possible C version).

520520
521-

# Early detection of an erroneous call to @lru_cache without any arguments

522-

# resulting in the inner function being passed to maxsize instead of an

523-

# integer or None. Negative maxsize is treated as 0.

524521

if isinstance(maxsize, int):

522+

# Negative maxsize is treated as 0

525523

if maxsize < 0:

526524

maxsize = 0

525+

elif callable(maxsize) and isinstance(typed, bool):

526+

# The user_function was passed in directly via the maxsize argument

527+

user_function, maxsize = maxsize, 128

528+

wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)

529+

return update_wrapper(wrapper, user_function)

527530

elif maxsize is not None:

528-

raise TypeError('Expected maxsize to be an integer or None')

531+

raise TypeError(

532+

'Expected first argument to be an integer, a callable, or None')

529533
530534

def decorating_function(user_function):

531535

wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)

Original file line numberDiff line numberDiff line change

@@ -1251,6 +1251,18 @@ def f(x):

12511251

self.assertEqual(misses, 4)

12521252

self.assertEqual(currsize, 2)

12531253
1254+

def test_lru_no_args(self):

1255+

@self.module.lru_cache

1256+

def square(x):

1257+

return x ** 2

1258+
1259+

self.assertEqual(list(map(square, [10, 20, 10])),

1260+

[100, 400, 100])

1261+

self.assertEqual(square.cache_info().hits, 1)

1262+

self.assertEqual(square.cache_info().misses, 2)

1263+

self.assertEqual(square.cache_info().maxsize, 128)

1264+

self.assertEqual(square.cache_info().currsize, 2)

1265+
12541266

def test_lru_bug_35780(self):

12551267

# C version of the lru_cache was not checking to see if

12561268

# the user function call has already modified the cache

@@ -1582,13 +1594,6 @@ def __eq__(self, other):

15821594

self.assertEqual(test_func(DoubleEq(2)), # Trigger a re-entrant __eq__ call

15831595

DoubleEq(2)) # Verify the correct return value

15841596
1585-

def test_early_detection_of_bad_call(self):

1586-

# Issue #22184

1587-

with self.assertRaises(TypeError):

1588-

@functools.lru_cache

1589-

def f():

1590-

pass

1591-
15921597

def test_lru_method(self):

15931598

class X(int):

15941599

f_cnt = 0

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,2 @@

1+

functools.lru_cache() can now be used as a straight decorator in

2+

addition to its existing usage as a function that returns a decorator.