Issue 13227: Option to make the lru_cache() type specific (suggested by Andrew Koenig).

This commit is contained in:
Raymond Hettinger 2011-10-20 08:57:45 -07:00
parent e3455c026a
commit cd9fdfd652
5 changed files with 49 additions and 12 deletions

View File

@ -40,7 +40,7 @@ The :mod:`functools` module defines the following functions:
.. versionadded:: 3.2
.. decorator:: lru_cache(maxsize=100)
.. decorator:: lru_cache(maxsize=100, 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
@ -52,6 +52,10 @@ The :mod:`functools` module defines the following functions:
If *maxsize* is set to None, the LRU feature is disabled and the cache
can grow without bound.
If *typed* is set to True, function arguments of different types will be
cached separately. For example, ``f(3)`` and ``f(3.0)`` will be treated
as distinct calls with distinct results.
To help measure the effectiveness of the cache and tune the *maxsize*
parameter, the wrapped function is instrumented with a :func:`cache_info`
function that returns a :term:`named tuple` showing *hits*, *misses*,
@ -67,8 +71,8 @@ The :mod:`functools` module defines the following functions:
An `LRU (least recently used) cache
<http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used>`_ works
best when more recent calls are the best predictors of upcoming calls (for
example, the most popular articles on a news server tend to change daily).
best when the most recent calls are the best predictors of upcoming calls (for
example, the most popular articles on a news server tend to change each day).
The cache's size limit assures that the cache does not grow without bound on
long-running processes such as web servers.
@ -111,6 +115,9 @@ The :mod:`functools` module defines the following functions:
.. versionadded:: 3.2
.. versionchanged:: 3.3
Added the *typed* option.
.. decorator:: total_ordering
Given a class defining one or more rich comparison ordering methods, this

View File

@ -121,12 +121,16 @@ except ImportError:
_CacheInfo = namedtuple("CacheInfo", "hits misses maxsize currsize")
def lru_cache(maxsize=100):
def lru_cache(maxsize=100, typed=False):
"""Least-recently-used cache decorator.
If *maxsize* is set to None, the LRU features are disabled and the cache
can grow without bound.
If *typed* is True, arguments of different types will be cached separately.
For example, f(3.0) and f(3) will be treated as distinct calls with
distinct results.
Arguments to the cached function must be hashable.
View the cache statistics named tuple (hits, misses, maxsize, currsize) with
@ -142,7 +146,7 @@ def lru_cache(maxsize=100):
# to allow the implementation to change (including a possible C version).
def decorating_function(user_function,
tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
*, tuple=tuple, sorted=sorted, map=map, len=len, type=type, KeyError=KeyError):
hits = misses = 0
kwd_mark = (object(),) # separates positional and keyword args
@ -156,7 +160,12 @@ def lru_cache(maxsize=100):
nonlocal hits, misses
key = args
if kwds:
key += kwd_mark + tuple(sorted(kwds.items()))
sorted_items = tuple(sorted(kwds.items()))
key += kwd_mark + sorted_items
if typed:
key += tuple(map(type, args))
if kwds:
key += tuple(type(v) for k, v in sorted_items)
try:
result = cache[key]
hits += 1
@ -177,7 +186,12 @@ def lru_cache(maxsize=100):
nonlocal hits, misses
key = args
if kwds:
key += kwd_mark + tuple(sorted(kwds.items()))
sorted_items = tuple(sorted(kwds.items()))
key += kwd_mark + sorted_items
if typed:
key += tuple(map(type, args))
if kwds:
key += tuple(type(v) for k, v in sorted_items)
with lock:
try:
result = cache[key]

View File

@ -207,7 +207,7 @@ def compile(pattern, flags=0):
def purge():
"Clear the regular expression caches"
_compile_typed.cache_clear()
_compile.cache_clear()
_compile_repl.cache_clear()
def template(pattern, flags=0):
@ -253,11 +253,8 @@ def escape(pattern):
_pattern_type = type(sre_compile.compile("", 0))
@functools.lru_cache(maxsize=500, typed=True)
def _compile(pattern, flags):
return _compile_typed(type(pattern), pattern, flags)
@functools.lru_cache(maxsize=500)
def _compile_typed(text_bytes_type, pattern, flags):
# internal: compile pattern
if isinstance(pattern, _pattern_type):
if flags:

View File

@ -734,6 +734,22 @@ class TestLRU(unittest.TestCase):
with self.assertRaises(IndexError):
func(15)
def test_lru_with_types(self):
for maxsize in (None, 100):
@functools.lru_cache(maxsize=maxsize, typed=True)
def square(x):
return x * x
self.assertEqual(square(3), 9)
self.assertEqual(type(square(3)), type(9))
self.assertEqual(square(3.0), 9.0)
self.assertEqual(type(square(3.0)), type(9.0))
self.assertEqual(square(x=3), 9)
self.assertEqual(type(square(x=3)), type(9))
self.assertEqual(square(x=3.0), 9.0)
self.assertEqual(type(square(x=3.0)), type(9.0))
self.assertEqual(square.cache_info().hits, 4)
self.assertEqual(square.cache_info().misses, 4)
def test_main(verbose=None):
test_classes = (
TestPartial,

View File

@ -319,6 +319,9 @@ Core and Builtins
Library
-------
- Issue #13227: functools.lru_cache() now has a option to distinguish
calls with different argument types.
- Issue #6090: zipfile raises a ValueError when a document with a timestamp
earlier than 1980 is provided. Patch contributed by Petri Lehtinen.