]> granicus.if.org Git - python/commitdiff
Issue 13227: Option to make the lru_cache() type specific (suggested by Andrew Koenig).
authorRaymond Hettinger <python@rcn.com>
Thu, 20 Oct 2011 15:57:45 +0000 (08:57 -0700)
committerRaymond Hettinger <python@rcn.com>
Thu, 20 Oct 2011 15:57:45 +0000 (08:57 -0700)
Doc/library/functools.rst
Lib/functools.py
Lib/re.py
Lib/test/test_functools.py
Misc/NEWS

index 2316e804899e91a7b711659472af880539f16b1b..4eaf54e97e4586a1ecf243c6931a4007996ccf07 100644 (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
index 038f284f06849c02632ed0d9c32b0adbf5424738..1abb37a8c86630891e71a80be44258f56df252bd 100644 (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]
index cdf597627f095e7ce36922e1880fb8013e830b30..4b90b3f3288e11b975aee2cf9ad94848b97efe52 100644 (file)
--- a/Lib/re.py
+++ b/Lib/re.py
@@ -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:
index a31c92ee790d0976c53dc0fa90aba506c3b21609..c4d9fe6ac27026b1d06406716c86a3e74caa2eff 100644 (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,
index cd6747e1539bafae8ec6f4c01101b0059c9598ec..3fe419b2bde78264465613aeffa3417368d34ee9 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.