diff options
author | Raymond Hettinger <python@rcn.com> | 2010-11-30 19:15:45 (GMT) |
---|---|---|
committer | Raymond Hettinger <python@rcn.com> | 2010-11-30 19:15:45 (GMT) |
commit | 7496b4171e6a34a82ba388fa0ef125d8b87aa2a1 (patch) | |
tree | ed8fc1ddcee597b3273caf464d2397c51bd66be4 | |
parent | d01df46848da8c500665cfd59b84a1ebf25f25a2 (diff) | |
download | cpython-7496b4171e6a34a82ba388fa0ef125d8b87aa2a1.zip cpython-7496b4171e6a34a82ba388fa0ef125d8b87aa2a1.tar.gz cpython-7496b4171e6a34a82ba388fa0ef125d8b87aa2a1.tar.bz2 |
Add example, tighten text, and minor clean-ups.
-rw-r--r-- | Doc/library/functools.rst | 58 | ||||
-rw-r--r-- | Doc/whatsnew/3.2.rst | 9 | ||||
-rw-r--r-- | Lib/functools.py | 4 | ||||
-rw-r--r-- | Lib/test/test_functools.py | 16 |
4 files changed, 50 insertions, 37 deletions
diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 5b65874..7c9b9ca 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -42,40 +42,52 @@ The :mod:`functools` module defines the following functions: .. versionadded:: 3.2 -.. decorator:: lru_cache(maxsize) +.. decorator:: lru_cache(maxsize=100) 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 function is periodically called with the same arguments. - The *maxsize* parameter defaults to 100. Since a dictionary is used to cache - results, the positional and keyword arguments to the function must be - hashable. + Since a dictionary is used to cache results, the positional and keyword + arguments to the function must be hashable. - The wrapped function is instrumented with a :attr:`cache_info` attribute that - can be called to retrieve a named tuple with the following fields: + 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*, + *maxsize* and *currsize*. - - :attr:`maxsize`: maximum cache size (as set by the *maxsize* parameter) - - :attr:`size`: current number of entries in the cache - - :attr:`hits`: number of successful cache lookups - - :attr:`misses`: number of unsuccessful cache lookups. - - These statistics are helpful for tuning the *maxsize* parameter and for measuring - the effectiveness of the cache. - - The wrapped function also has a :attr:`cache_clear` attribute which can be - called (with no arguments) to clear the cache. + The decorator also provides a :func:`cache_clear` function for clearing or + invalidating the cache. The original underlying function is accessible through the - :attr:`__wrapped__` attribute. This allows introspection, bypassing - the cache, or rewrapping the function with a different caching tool. + :attr:`__wrapped__` attribute. This is useful for introspection, for + bypassing the cache, or for rewrapping the function with a different cache. A `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 each day). The cache's size limit assurs that caching does not - grow without bound on long-running processes such as web servers. + <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). + The cache's size limit assures that the cache does not grow without bound on + long-running processes such as web servers. + + Example -- Caching static web content:: + + @functools.lru_cache(maxsize=20) + def get_pep(num): + 'Retrieve text of a Python Enhancement Proposal' + resource = 'http://www.python.org/dev/peps/pep-%04d/' % num + try: + with urllib.request.urlopen(resource) as s: + return s.read() + except urllib.error.HTTPError: + return 'Not Found' + + >>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991: + ... pep = get_pep(n) + ... print(n, len(pep)) + + >>> print(get_pep.cache_info()) + CacheInfo(hits=3, misses=8, maxsize=20, currsize=8) .. versionadded:: 3.2 diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index 056e2fa..bc5e84e 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -332,13 +332,14 @@ New, Improved, and Deprecated Modules c.execute('SELECT phonenumber FROM phonelist WHERE name=?', (name,)) return c.fetchone()[0] + >>> for name in user_requests: + ... get_phone_number(name) # cached lookup + To help with choosing an effective cache size, the wrapped function is - instrumented with info function: + instrumented for tracking cache statistics: - >>> for name in user_requests: - ... get_phone_number(name) >>> get_phone_number.cache_info() - CacheInfo(maxsize=300, size=300, hits=4805, misses=980) + CacheInfo(hits=4805, misses=980, maxsize=300, currsize=300) If the phonelist table gets updated, the outdated contents of the cache can be cleared with: diff --git a/Lib/functools.py b/Lib/functools.py index e8e9960..c558a5e 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -114,7 +114,7 @@ def cmp_to_key(mycmp): raise TypeError('hash not implemented') return K -_CacheInfo = namedtuple("CacheInfo", "maxsize, size, hits, misses") +_CacheInfo = namedtuple("CacheInfo", "hits misses maxsize currsize") def lru_cache(maxsize=100): """Least-recently-used cache decorator. @@ -166,7 +166,7 @@ def lru_cache(maxsize=100): def cache_info(): """Report cache statistics""" with lock: - return _CacheInfo(maxsize, len(cache), hits, misses) + return _CacheInfo(hits, misses, maxsize, len(cache)) def cache_clear(): """Clear the cache and cache statistics""" diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index c877f88..8f48e9e 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -501,7 +501,7 @@ class TestLRU(unittest.TestCase): def orig(x, y): return 3*x+y f = functools.lru_cache(maxsize=20)(orig) - maxsize, currsize, hits, misses = f.cache_info() + hits, misses, maxsize, currsize = f.cache_info() self.assertEqual(maxsize, 20) self.assertEqual(currsize, 0) self.assertEqual(hits, 0) @@ -513,18 +513,18 @@ class TestLRU(unittest.TestCase): actual = f(x, y) expected = orig(x, y) self.assertEqual(actual, expected) - maxsize, currsize, hits, misses = f.cache_info() + hits, misses, maxsize, currsize = f.cache_info() self.assertTrue(hits > misses) self.assertEqual(hits + misses, 1000) self.assertEqual(currsize, 20) f.cache_clear() # test clearing - maxsize, currsize, hits, misses = f.cache_info() + hits, misses, maxsize, currsize = f.cache_info() self.assertEqual(hits, 0) self.assertEqual(misses, 0) self.assertEqual(currsize, 0) f(x, y) - maxsize, currsize, hits, misses = f.cache_info() + hits, misses, maxsize, currsize = f.cache_info() self.assertEqual(hits, 0) self.assertEqual(misses, 1) self.assertEqual(currsize, 1) @@ -532,7 +532,7 @@ class TestLRU(unittest.TestCase): # Test bypassing the cache self.assertIs(f.__wrapped__, orig) f.__wrapped__(x, y) - maxsize, currsize, hits, misses = f.cache_info() + hits, misses, maxsize, currsize = f.cache_info() self.assertEqual(hits, 0) self.assertEqual(misses, 1) self.assertEqual(currsize, 1) @@ -548,7 +548,7 @@ class TestLRU(unittest.TestCase): for i in range(5): self.assertEqual(f(), 20) self.assertEqual(f_cnt, 5) - maxsize, currsize, hits, misses = f.cache_info() + hits, misses, maxsize, currsize = f.cache_info() self.assertEqual(hits, 0) self.assertEqual(misses, 5) self.assertEqual(currsize, 0) @@ -564,7 +564,7 @@ class TestLRU(unittest.TestCase): for i in range(5): self.assertEqual(f(), 20) self.assertEqual(f_cnt, 1) - maxsize, currsize, hits, misses = f.cache_info() + hits, misses, maxsize, currsize = f.cache_info() self.assertEqual(hits, 4) self.assertEqual(misses, 1) self.assertEqual(currsize, 1) @@ -581,7 +581,7 @@ class TestLRU(unittest.TestCase): # * * * * self.assertEqual(f(x), x*10) self.assertEqual(f_cnt, 4) - maxsize, currsize, hits, misses = f.cache_info() + hits, misses, maxsize, currsize = f.cache_info() self.assertEqual(hits, 12) self.assertEqual(misses, 4) self.assertEqual(currsize, 2) |