Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9e46ef8

Browse files
committed
Add functools.lfu_cache() and functools.lru_cache().
1 parent 17e3d69 commit 9e46ef8

4 files changed

Lines changed: 193 additions & 2 deletions

File tree

Doc/library/functools.rst

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,57 @@ The :mod:`functools` module defines the following functions:
3737

3838
.. versionadded:: 3.2
3939

40+
.. decorator:: lfu_cache(maxsize)
41+
42+
Decorator to wrap a function with a memoizing callable that saves up to the
43+
*maxsize* most frequent calls. It can save time when an expensive or I/O
44+
bound function is periodically called with the same arguments.
45+
46+
The *maxsize* parameter defaults to 100. Since a dictionary is used to cache
47+
results, the positional and keyword arguments to the function must be
48+
hashable.
49+
50+
The wrapped function is instrumented with two attributes, :attr:`hits`
51+
and :attr:`misses` which count the number of successful or unsuccessful
52+
cache lookups. These statistics are helpful for tuning the *maxsize*
53+
parameter and for measuring the cache's effectiveness.
54+
55+
The wrapped function also has a :attr:`clear` attribute which can be
56+
called (with no arguments) to clear the cache.
57+
58+
A `LFU (least frequently used) cache
59+
<http://en.wikipedia.org/wiki/Cache_algorithms#Least-Frequently_Used>`_
60+
is indicated when the pattern of calls does not change over time, when
61+
more the most common calls already seen are the best predictors of the
62+
most common upcoming calls.
63+
64+
.. versionadded:: 3.2
65+
66+
.. decorator:: lru_cache(maxsize)
67+
68+
Decorator to wrap a function with a memoizing callable that saves up to the
69+
*maxsize* most recent calls. It can save time when an expensive or I/O bound
70+
function is periodically called with the same arguments.
71+
72+
The *maxsize* parameter defaults to 100. Since a dictionary is used to cache
73+
results, the positional and keyword arguments to the function must be
74+
hashable.
75+
76+
The wrapped function is instrumented with two attributes, :attr:`hits`
77+
and :attr:`misses` which count the number of successful or unsuccessful
78+
cache lookups. These statistics are helpful for tuning the *maxsize*
79+
parameter and for measuring the cache's effectiveness.
80+
81+
The wrapped function also has a :attr:`clear` attribute which can be
82+
called (with no arguments) to clear the cache.
83+
84+
A `LRU (least recently used) cache
85+
<http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used>`_
86+
is indicated when the pattern of calls changes over time, such as
87+
when more recent calls are the best predictors of upcoming calls.
88+
89+
.. versionadded:: 3.2
90+
4091
.. decorator:: total_ordering
4192

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

Lib/functools.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@
44
# to allow utilities written in Python to be added
55
# to the functools module.
66
# Written by Nick Coghlan <ncoghlan at gmail.com>
7-
# Copyright (C) 2006 Python Software Foundation.
7+
# and Raymond Hettinger <python at rcn.com>
8+
# Copyright (C) 2006-2010 Python Software Foundation.
89
# See C source code for _functools credits/copyright
910

11+
__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
12+
'total_ordering', 'cmp_to_key', 'lfu_cache', 'lru_cache']
13+
1014
from _functools import partial, reduce
15+
from collections import OrderedDict, Counter
16+
from heapq import nsmallest
17+
from operator import itemgetter
1118

1219
# update_wrapper() and wraps() are tools to help write
1320
# wrapper functions that can handle naive introspection
@@ -97,3 +104,88 @@ def __ne__(self, other):
97104
def __hash__(self):
98105
raise TypeError('hash not implemented')
99106
return K
107+
108+
def lfu_cache(maxsize=100):
109+
'''Least-frequently-used cache decorator.
110+
111+
Arguments to the cached function must be hashable.
112+
Cache performance statistics stored in f.hits and f.misses.
113+
Clear the cache using f.clear().
114+
http://en.wikipedia.org/wiki/Cache_algorithms#Least-Frequently_Used
115+
116+
'''
117+
def decorating_function(user_function):
118+
cache = {} # mapping of args to results
119+
use_count = Counter() # times each key has been accessed
120+
kwd_mark = object() # separate positional and keyword args
121+
122+
@wraps(user_function)
123+
def wrapper(*args, **kwds):
124+
key = args
125+
if kwds:
126+
key += (kwd_mark,) + tuple(sorted(kwds.items()))
127+
use_count[key] += 1 # count a use of this key
128+
try:
129+
result = cache[key]
130+
wrapper.hits += 1
131+
except KeyError:
132+
result = user_function(*args, **kwds)
133+
cache[key] = result
134+
wrapper.misses += 1
135+
if len(cache) > maxsize:
136+
# purge the 10% least frequently used entries
137+
for key, _ in nsmallest(maxsize // 10,
138+
use_count.items(),
139+
key=itemgetter(1)):
140+
del cache[key], use_count[key]
141+
return result
142+
143+
def clear():
144+
'Clear the cache and cache statistics'
145+
cache.clear()
146+
use_count.clear()
147+
wrapper.hits = wrapper.misses = 0
148+
149+
wrapper.hits = wrapper.misses = 0
150+
wrapper.clear = clear
151+
return wrapper
152+
return decorating_function
153+
154+
def lru_cache(maxsize=100):
155+
'''Least-recently-used cache decorator.
156+
157+
Arguments to the cached function must be hashable.
158+
Cache performance statistics stored in f.hits and f.misses.
159+
Clear the cache using f.clear().
160+
http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
161+
162+
'''
163+
def decorating_function(user_function):
164+
cache = OrderedDict() # ordered least recent to most recent
165+
kwd_mark = object() # separate positional and keyword args
166+
167+
@wraps(user_function)
168+
def wrapper(*args, **kwds):
169+
key = args
170+
if kwds:
171+
key += (kwd_mark,) + tuple(sorted(kwds.items()))
172+
try:
173+
result = cache.pop(key)
174+
wrapper.hits += 1
175+
except KeyError:
176+
result = user_function(*args, **kwds)
177+
wrapper.misses += 1
178+
if len(cache) >= maxsize:
179+
cache.popitem(0) # purge least recently used cache entry
180+
cache[key] = result # record recent use of this key
181+
return result
182+
183+
def clear():
184+
'Clear the cache and cache statistics'
185+
cache.clear()
186+
wrapper.hits = wrapper.misses = 0
187+
188+
wrapper.hits = wrapper.misses = 0
189+
wrapper.clear = clear
190+
return wrapper
191+
return decorating_function

Lib/test/test_functools.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from test import support
55
from weakref import proxy
66
import pickle
7+
from random import choice
78

89
@staticmethod
910
def PythonPartial(func, *args, **keywords):
@@ -454,14 +455,59 @@ def test_no_operations_defined(self):
454455
class A:
455456
pass
456457

458+
class TestLRU(unittest.TestCase):
459+
460+
def test_lru(self):
461+
def orig(x, y):
462+
return 3*x+y
463+
f = functools.lru_cache(maxsize=20)(orig)
464+
465+
domain = range(5)
466+
for i in range(1000):
467+
x, y = choice(domain), choice(domain)
468+
actual = f(x, y)
469+
expected = orig(x, y)
470+
self.assertEquals(actual, expected)
471+
self.assert_(f.hits > f.misses)
472+
self.assertEquals(f.hits + f.misses, 1000)
473+
474+
f.clear() # test clearing
475+
self.assertEqual(f.hits, 0)
476+
self.assertEqual(f.misses, 0)
477+
f(x, y)
478+
self.assertEqual(f.hits, 0)
479+
self.assertEqual(f.misses, 1)
480+
481+
def test_lfu(self):
482+
def orig(x, y):
483+
return 3*x+y
484+
f = functools.lfu_cache(maxsize=20)(orig)
485+
486+
domain = range(5)
487+
for i in range(1000):
488+
x, y = choice(domain), choice(domain)
489+
actual = f(x, y)
490+
expected = orig(x, y)
491+
self.assertEquals(actual, expected)
492+
self.assert_(f.hits > f.misses)
493+
self.assertEquals(f.hits + f.misses, 1000)
494+
495+
f.clear() # test clearing
496+
self.assertEqual(f.hits, 0)
497+
self.assertEqual(f.misses, 0)
498+
f(x, y)
499+
self.assertEqual(f.hits, 0)
500+
self.assertEqual(f.misses, 1)
501+
457502
def test_main(verbose=None):
458503
test_classes = (
459504
TestPartial,
460505
TestPartialSubclass,
461506
TestPythonPartial,
462507
TestUpdateWrapper,
463508
TestWraps,
464-
TestReduce
509+
TestReduce,
510+
TestLRU,
465511
)
466512
support.run_unittest(*test_classes)
467513

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,8 @@ Library
504504
- Issue #4179: In pdb, allow "list ." as a command to return to the currently
505505
debugged line.
506506

507+
- Add lfu_cache() and lru_cache() decorators to the functools module.
508+
507509
- Issue #4108: In urllib.robotparser, if there are multiple 'User-agent: *'
508510
entries, consider the first one.
509511

0 commit comments

Comments
 (0)