Skip to content

Commit cd9fdfd

Browse files
committed
Issue 13227: Option to make the lru_cache() type specific (suggested by Andrew Koenig).
1 parent e3455c0 commit cd9fdfd

File tree

5 files changed

+49
-12
lines changed

5 files changed

+49
-12
lines changed

Doc/library/functools.rst

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ The :mod:`functools` module defines the following functions:
4040
.. versionadded:: 3.2
4141

4242

43-
.. decorator:: lru_cache(maxsize=100)
43+
.. decorator:: lru_cache(maxsize=100, typed=False)
4444

4545
Decorator to wrap a function with a memoizing callable that saves up to the
4646
*maxsize* most recent calls. It can save time when an expensive or I/O bound
@@ -52,6 +52,10 @@ The :mod:`functools` module defines the following functions:
5252
If *maxsize* is set to None, the LRU feature is disabled and the cache
5353
can grow without bound.
5454

55+
If *typed* is set to True, function arguments of different types will be
56+
cached separately. For example, ``f(3)`` and ``f(3.0)`` will be treated
57+
as distinct calls with distinct results.
58+
5559
To help measure the effectiveness of the cache and tune the *maxsize*
5660
parameter, the wrapped function is instrumented with a :func:`cache_info`
5761
function that returns a :term:`named tuple` showing *hits*, *misses*,
@@ -67,8 +71,8 @@ The :mod:`functools` module defines the following functions:
6771

6872
An `LRU (least recently used) cache
6973
<http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used>`_ works
70-
best when more recent calls are the best predictors of upcoming calls (for
71-
example, the most popular articles on a news server tend to change daily).
74+
best when the most recent calls are the best predictors of upcoming calls (for
75+
example, the most popular articles on a news server tend to change each day).
7276
The cache's size limit assures that the cache does not grow without bound on
7377
long-running processes such as web servers.
7478

@@ -111,6 +115,9 @@ The :mod:`functools` module defines the following functions:
111115

112116
.. versionadded:: 3.2
113117

118+
.. versionchanged:: 3.3
119+
Added the *typed* option.
120+
114121
.. decorator:: total_ordering
115122

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

Lib/functools.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,16 @@ def __ne__(self, other):
121121

122122
_CacheInfo = namedtuple("CacheInfo", "hits misses maxsize currsize")
123123

124-
def lru_cache(maxsize=100):
124+
def lru_cache(maxsize=100, typed=False):
125125
"""Least-recently-used cache decorator.
126126
127127
If *maxsize* is set to None, the LRU features are disabled and the cache
128128
can grow without bound.
129129
130+
If *typed* is True, arguments of different types will be cached separately.
131+
For example, f(3.0) and f(3) will be treated as distinct calls with
132+
distinct results.
133+
130134
Arguments to the cached function must be hashable.
131135
132136
View the cache statistics named tuple (hits, misses, maxsize, currsize) with
@@ -142,7 +146,7 @@ def lru_cache(maxsize=100):
142146
# to allow the implementation to change (including a possible C version).
143147

144148
def decorating_function(user_function,
145-
tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
149+
*, tuple=tuple, sorted=sorted, map=map, len=len, type=type, KeyError=KeyError):
146150

147151
hits = misses = 0
148152
kwd_mark = (object(),) # separates positional and keyword args
@@ -156,7 +160,12 @@ def wrapper(*args, **kwds):
156160
nonlocal hits, misses
157161
key = args
158162
if kwds:
159-
key += kwd_mark + tuple(sorted(kwds.items()))
163+
sorted_items = tuple(sorted(kwds.items()))
164+
key += kwd_mark + sorted_items
165+
if typed:
166+
key += tuple(map(type, args))
167+
if kwds:
168+
key += tuple(type(v) for k, v in sorted_items)
160169
try:
161170
result = cache[key]
162171
hits += 1
@@ -177,7 +186,12 @@ def wrapper(*args, **kwds):
177186
nonlocal hits, misses
178187
key = args
179188
if kwds:
180-
key += kwd_mark + tuple(sorted(kwds.items()))
189+
sorted_items = tuple(sorted(kwds.items()))
190+
key += kwd_mark + sorted_items
191+
if typed:
192+
key += tuple(map(type, args))
193+
if kwds:
194+
key += tuple(type(v) for k, v in sorted_items)
181195
with lock:
182196
try:
183197
result = cache[key]

Lib/re.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def compile(pattern, flags=0):
207207

208208
def purge():
209209
"Clear the regular expression caches"
210-
_compile_typed.cache_clear()
210+
_compile.cache_clear()
211211
_compile_repl.cache_clear()
212212

213213
def template(pattern, flags=0):
@@ -253,11 +253,8 @@ def escape(pattern):
253253

254254
_pattern_type = type(sre_compile.compile("", 0))
255255

256+
@functools.lru_cache(maxsize=500, typed=True)
256257
def _compile(pattern, flags):
257-
return _compile_typed(type(pattern), pattern, flags)
258-
259-
@functools.lru_cache(maxsize=500)
260-
def _compile_typed(text_bytes_type, pattern, flags):
261258
# internal: compile pattern
262259
if isinstance(pattern, _pattern_type):
263260
if flags:

Lib/test/test_functools.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,22 @@ def func(i):
734734
with self.assertRaises(IndexError):
735735
func(15)
736736

737+
def test_lru_with_types(self):
738+
for maxsize in (None, 100):
739+
@functools.lru_cache(maxsize=maxsize, typed=True)
740+
def square(x):
741+
return x * x
742+
self.assertEqual(square(3), 9)
743+
self.assertEqual(type(square(3)), type(9))
744+
self.assertEqual(square(3.0), 9.0)
745+
self.assertEqual(type(square(3.0)), type(9.0))
746+
self.assertEqual(square(x=3), 9)
747+
self.assertEqual(type(square(x=3)), type(9))
748+
self.assertEqual(square(x=3.0), 9.0)
749+
self.assertEqual(type(square(x=3.0)), type(9.0))
750+
self.assertEqual(square.cache_info().hits, 4)
751+
self.assertEqual(square.cache_info().misses, 4)
752+
737753
def test_main(verbose=None):
738754
test_classes = (
739755
TestPartial,

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,9 @@ Core and Builtins
319319
Library
320320
-------
321321

322+
- Issue #13227: functools.lru_cache() now has a option to distinguish
323+
calls with different argument types.
324+
322325
- Issue #6090: zipfile raises a ValueError when a document with a timestamp
323326
earlier than 1980 is provided. Patch contributed by Petri Lehtinen.
324327

0 commit comments

Comments
 (0)