Index: Lib/test/test_functools.py =================================================================== --- Lib/test/test_functools.py (revision 86857) +++ Lib/test/test_functools.py (working copy) @@ -501,6 +501,10 @@ def orig(x, y): return 3*x+y f = functools.lru_cache(maxsize=20)(orig) + self.assertEqual(f.cache.maxsize, 20) + self.assertEqual(f.cache.hits, 0) + self.assertEqual(f.cache.misses, 0) + self.assertEqual(len(f.cache), 0) domain = range(5) for i in range(1000): @@ -508,21 +512,25 @@ actual = f(x, y) expected = orig(x, y) self.assertEqual(actual, expected) - self.assertTrue(f.cache_hits > f.cache_misses) - self.assertEqual(f.cache_hits + f.cache_misses, 1000) + self.assertTrue(f.cache.hits > f.cache.misses) + self.assertEqual(f.cache.hits + f.cache.misses, 1000) + self.assertEqual(len(f.cache), 20) - f.cache_clear() # test clearing - self.assertEqual(f.cache_hits, 0) - self.assertEqual(f.cache_misses, 0) + f.cache.clear() # test clearing + self.assertEqual(f.cache.hits, 0) + self.assertEqual(f.cache.misses, 0) + self.assertEqual(len(f.cache), 0) f(x, y) - self.assertEqual(f.cache_hits, 0) - self.assertEqual(f.cache_misses, 1) + self.assertEqual(f.cache.hits, 0) + self.assertEqual(f.cache.misses, 1) + self.assertEqual(len(f.cache), 1) # Test bypassing the cache self.assertIs(f.__wrapped__, orig) f.__wrapped__(x, y) - self.assertEqual(f.cache_hits, 0) - self.assertEqual(f.cache_misses, 1) + self.assertEqual(f.cache.hits, 0) + self.assertEqual(f.cache.misses, 1) + self.assertEqual(len(f.cache), 1) # test size zero (which means "never-cache") @functools.lru_cache(0) @@ -530,10 +538,14 @@ nonlocal f_cnt f_cnt += 1 return 20 + self.assertEqual(f.cache.maxsize, 0) f_cnt = 0 for i in range(5): self.assertEqual(f(), 20) self.assertEqual(f_cnt, 5) + self.assertEqual(f.cache.hits, 0) + self.assertEqual(f.cache.misses, 5) + self.assertEqual(len(f.cache), 0) # test size one @functools.lru_cache(1) @@ -541,10 +553,14 @@ nonlocal f_cnt f_cnt += 1 return 20 + self.assertEqual(f.cache.maxsize, 1) f_cnt = 0 for i in range(5): self.assertEqual(f(), 20) self.assertEqual(f_cnt, 1) + self.assertEqual(f.cache.hits, 4) + self.assertEqual(f.cache.misses, 1) + self.assertEqual(len(f.cache), 1) # test size two @functools.lru_cache(2) @@ -552,11 +568,15 @@ nonlocal f_cnt f_cnt += 1 return x*10 + self.assertEqual(f.cache.maxsize, 2) f_cnt = 0 for x in 7, 9, 7, 9, 7, 9, 8, 8, 8, 9, 9, 9, 8, 8, 8, 7: # * * * * self.assertEqual(f(x), x*10) self.assertEqual(f_cnt, 4) + self.assertEqual(f.cache.hits, 12) + self.assertEqual(f.cache.misses, 4) + self.assertEqual(len(f.cache), 2) def test_main(verbose=None): test_classes = ( Index: Lib/functools.py =================================================================== --- Lib/functools.py (revision 86857) +++ Lib/functools.py (working copy) @@ -127,7 +127,7 @@ """ # Users should only access the lru_cache through its public API: - # cache_hits, cache_misses, cache_clear(), and __wrapped__ + # f.cache, and f.__wrapped__ # The internals of the lru_cache are encapsulated for thread safety and # to allow the implementation to change (including a possible C version). @@ -137,11 +137,13 @@ cache = OrderedDict() # ordered least recent to most recent cache_popitem = cache.popitem cache_renew = cache.move_to_end + cache_hits = cache_misses = 0 kwd_mark = object() # separate positional and keyword args lock = Lock() @wraps(user_function) def wrapper(*args, **kwds): + nonlocal cache_hits, cache_misses key = args if kwds: key += (kwd_mark,) + tuple(sorted(kwds.items())) @@ -149,24 +151,40 @@ with lock: result = cache[key] cache_renew(key) # record recent use of this key - wrapper.cache_hits += 1 + cache_hits += 1 except KeyError: result = user_function(*args, **kwds) with lock: cache[key] = result # record recent use of this key - wrapper.cache_misses += 1 + cache_misses += 1 if len(cache) > maxsize: cache_popitem(0) # purge least recently used cache entry return result def cache_clear(): """Clear the cache and cache statistics""" + nonlocal cache_hits, cache_misses with lock: cache.clear() - wrapper.cache_hits = wrapper.cache_misses = 0 + cache_hits = cache_misses = 0 - wrapper.cache_hits = wrapper.cache_misses = 0 - wrapper.cache_clear = cache_clear + class CacheInfo: + "Public API for internal cache status" + @property + def hits(self): + return cache_hits + @property + def misses(self): + return cache_misses + @property + def maxsize(self): + return maxsize # Picks up the argument value + def clear(self): + cache_clear() + def __len__(self): + return len(cache) + + wrapper.cache = CacheInfo() return wrapper return decorating_function