Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c79fb0e

Browse files
committed
Issue 10593: Adopt Nick's suggestion for an lru_cache with maxsize=None.
1 parent ed3a7d2 commit c79fb0e

3 files changed

Lines changed: 78 additions & 25 deletions

File tree

Doc/library/functools.rst

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The :mod:`functools` module defines the following functions:
3232
A compare function is any callable that accept two arguments, compares them,
3333
and returns a negative number for less-than, zero for equality, or a positive
3434
number for greater-than. A key function is a callable that accepts one
35-
argument and returns another value that indicates the position in the desired
35+
argument and returns another value indicating the position in the desired
3636
collation sequence.
3737

3838
Example::
@@ -51,10 +51,14 @@ The :mod:`functools` module defines the following functions:
5151
Since a dictionary is used to cache results, the positional and keyword
5252
arguments to the function must be hashable.
5353

54+
If *maxsize* is set to None, the LRU feature is disabled and the cache
55+
can grow without bound.
56+
5457
To help measure the effectiveness of the cache and tune the *maxsize*
5558
parameter, the wrapped function is instrumented with a :func:`cache_info`
5659
function that returns a :term:`named tuple` showing *hits*, *misses*,
57-
*maxsize* and *currsize*.
60+
*maxsize* and *currsize*. In a multi-threaded environment, the hits
61+
and misses are approximate.
5862

5963
The decorator also provides a :func:`cache_clear` function for clearing or
6064
invalidating the cache.
@@ -89,12 +93,25 @@ The :mod:`functools` module defines the following functions:
8993
>>> print(get_pep.cache_info())
9094
CacheInfo(hits=3, misses=8, maxsize=20, currsize=8)
9195

92-
.. versionadded:: 3.2
96+
Example of efficiently computing
97+
`Fibonacci numbers <http://en.wikipedia.org/wiki/Fibonacci_number>`_
98+
using a cache to implement a
99+
`dynamic programming <http://en.wikipedia.org/wiki/Dynamic_programming>`_
100+
technique::
101+
102+
@lru_cache(maxsize=None)
103+
def fib(n):
104+
if n < 2:
105+
return n
106+
return fib(n-1) + fib(n-2)
93107

94-
.. seealso::
108+
>>> print([fib(n) for n in range(16)])
109+
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
95110

96-
Recipe for a `plain cache without the LRU feature
97-
<http://code.activestate.com/recipes/577479-simple-caching-decorator/>`_.
111+
>>> print(fib.cache_info())
112+
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)
113+
114+
.. versionadded:: 3.2
98115

99116
.. decorator:: total_ordering
100117

Lib/functools.py

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ def __hash__(self):
119119
def lru_cache(maxsize=100):
120120
"""Least-recently-used cache decorator.
121121
122+
If *maxsize* is set to None, the LRU features are disabled and the cache
123+
can grow without bound.
124+
122125
Arguments to the cached function must be hashable.
123126
124127
View the cache statistics named tuple (hits, misses, maxsize, currsize) with
@@ -136,32 +139,51 @@ def lru_cache(maxsize=100):
136139
def decorating_function(user_function,
137140
tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
138141

139-
cache = OrderedDict() # ordered least recent to most recent
140-
cache_popitem = cache.popitem
141-
cache_renew = cache.move_to_end
142142
hits = misses = 0
143143
kwd_mark = object() # separates positional and keyword args
144144
lock = Lock()
145145

146-
@wraps(user_function)
147-
def wrapper(*args, **kwds):
148-
nonlocal hits, misses
149-
key = args
150-
if kwds:
151-
key += (kwd_mark,) + tuple(sorted(kwds.items()))
152-
try:
153-
with lock:
146+
if maxsize is None:
147+
cache = dict() # simple cache without ordering or size limit
148+
149+
@wraps(user_function)
150+
def wrapper(*args, **kwds):
151+
nonlocal hits, misses
152+
key = args
153+
if kwds:
154+
key += (kwd_mark,) + tuple(sorted(kwds.items()))
155+
try:
154156
result = cache[key]
155-
cache_renew(key) # record recent use of this key
156157
hits += 1
157-
except KeyError:
158-
result = user_function(*args, **kwds)
159-
with lock:
160-
cache[key] = result # record recent use of this key
158+
except KeyError:
159+
result = user_function(*args, **kwds)
160+
cache[key] = result
161161
misses += 1
162-
if len(cache) > maxsize:
163-
cache_popitem(0) # purge least recently used cache entry
164-
return result
162+
return result
163+
else:
164+
cache = OrderedDict() # ordered least recent to most recent
165+
cache_popitem = cache.popitem
166+
cache_renew = cache.move_to_end
167+
168+
@wraps(user_function)
169+
def wrapper(*args, **kwds):
170+
nonlocal hits, misses
171+
key = args
172+
if kwds:
173+
key += (kwd_mark,) + tuple(sorted(kwds.items()))
174+
try:
175+
with lock:
176+
result = cache[key]
177+
cache_renew(key) # record recent use of this key
178+
hits += 1
179+
except KeyError:
180+
result = user_function(*args, **kwds)
181+
with lock:
182+
cache[key] = result # record recent use of this key
183+
misses += 1
184+
if len(cache) > maxsize:
185+
cache_popitem(0) # purge least recently used cache entry
186+
return result
165187

166188
def cache_info():
167189
"""Report cache statistics"""

Lib/test/test_functools.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,20 @@ def f(x):
586586
self.assertEqual(misses, 4)
587587
self.assertEqual(currsize, 2)
588588

589+
def test_lru_with_maxsize_none(self):
590+
@functools.lru_cache(maxsize=None)
591+
def fib(n):
592+
if n < 2:
593+
return n
594+
return fib(n-1) + fib(n-2)
595+
self.assertEqual([fib(n) for n in range(16)],
596+
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610])
597+
self.assertEqual(fib.cache_info(),
598+
functools._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16))
599+
fib.cache_clear()
600+
self.assertEqual(fib.cache_info(),
601+
functools._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))
602+
589603
def test_main(verbose=None):
590604
test_classes = (
591605
TestPartial,

0 commit comments

Comments
 (0)