Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Doc/library/functools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ The :mod:`functools` module defines the following functions:
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*. In a multi-threaded environment, the hits
*maxsize*, *currsize* and *typed*. In a multi-threaded environment, the hits
and misses are approximate.

The decorator also provides a :func:`cache_clear` function for clearing or
Expand Down
4 changes: 2 additions & 2 deletions Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ def _unwrap_partial(func):
### LRU Cache function decorator
################################################################################

_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize", "typed"])

class _HashedSeq(list):
""" This class guarantees that hash() will be called no more than once
Expand Down Expand Up @@ -611,7 +611,7 @@ def wrapper(*args, **kwds):
def cache_info():
"""Report cache statistics"""
with lock:
return _CacheInfo(hits, misses, maxsize, cache_len())
return _CacheInfo(hits, misses, maxsize, cache_len(), typed)

def cache_clear():
"""Clear the cache and cache statistics"""
Expand Down
66 changes: 38 additions & 28 deletions Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1164,41 +1164,46 @@ def test_lru(self):
def orig(x, y):
return 3 * x + y
f = self.module.lru_cache(maxsize=20)(orig)
hits, misses, maxsize, currsize = f.cache_info()
hits, misses, maxsize, currsize, typed = f.cache_info()
self.assertEqual(maxsize, 20)
self.assertEqual(currsize, 0)
self.assertEqual(hits, 0)
self.assertEqual(misses, 0)
self.assertEqual(typed, False)

domain = range(5)
for i in range(1000):
x, y = choice(domain), choice(domain)
actual = f(x, y)
expected = orig(x, y)
self.assertEqual(actual, expected)
hits, misses, maxsize, currsize = f.cache_info()
hits, misses, maxsize, currsize, typed = f.cache_info()
self.assertTrue(hits > misses)
self.assertEqual(hits + misses, 1000)
self.assertEqual(currsize, 20)
self.assertEqual(typed, False)

f.cache_clear() # test clearing
hits, misses, maxsize, currsize = f.cache_info()
hits, misses, maxsize, currsize, typed = f.cache_info()
self.assertEqual(hits, 0)
self.assertEqual(misses, 0)
self.assertEqual(currsize, 0)
self.assertEqual(typed, False)
f(x, y)
hits, misses, maxsize, currsize = f.cache_info()
hits, misses, maxsize, currsize, typed = f.cache_info()
self.assertEqual(hits, 0)
self.assertEqual(misses, 1)
self.assertEqual(currsize, 1)
self.assertEqual(typed, False)

# Test bypassing the cache
self.assertIs(f.__wrapped__, orig)
f.__wrapped__(x, y)
hits, misses, maxsize, currsize = f.cache_info()
hits, misses, maxsize, currsize, typed = f.cache_info()
self.assertEqual(hits, 0)
self.assertEqual(misses, 1)
self.assertEqual(currsize, 1)
self.assertEqual(typed, False)

# test size zero (which means "never-cache")
@self.module.lru_cache(0)
Expand All @@ -1211,10 +1216,11 @@ def f():
for i in range(5):
self.assertEqual(f(), 20)
self.assertEqual(f_cnt, 5)
hits, misses, maxsize, currsize = f.cache_info()
hits, misses, maxsize, currsize, typed = f.cache_info()
self.assertEqual(hits, 0)
self.assertEqual(misses, 5)
self.assertEqual(currsize, 0)
self.assertEqual(typed, False)

# test size one
@self.module.lru_cache(1)
Expand All @@ -1227,10 +1233,11 @@ def f():
for i in range(5):
self.assertEqual(f(), 20)
self.assertEqual(f_cnt, 1)
hits, misses, maxsize, currsize = f.cache_info()
hits, misses, maxsize, currsize, typed = f.cache_info()
self.assertEqual(hits, 4)
self.assertEqual(misses, 1)
self.assertEqual(currsize, 1)
self.assertEqual(typed, False)

# test size two
@self.module.lru_cache(2)
Expand All @@ -1244,10 +1251,11 @@ def f(x):
# * * * *
self.assertEqual(f(x), x*10)
self.assertEqual(f_cnt, 4)
hits, misses, maxsize, currsize = f.cache_info()
hits, misses, maxsize, currsize, typed = f.cache_info()
self.assertEqual(hits, 12)
self.assertEqual(misses, 4)
self.assertEqual(currsize, 2)
self.assertEqual(typed, False)

def test_lru_no_args(self):
@self.module.lru_cache
Expand Down Expand Up @@ -1320,22 +1328,22 @@ def f(x, y):
# Add to cache: One use as an argument gives one call
self.assertEqual(f(mock_int, 1), 16)
self.assertEqual(mock_int.__hash__.call_count, 1)
self.assertEqual(f.cache_info(), (0, 1, 1, 1))
self.assertEqual(f.cache_info(), (0, 1, 1, 1, False))

# Cache hit: One use as an argument gives one additional call
self.assertEqual(f(mock_int, 1), 16)
self.assertEqual(mock_int.__hash__.call_count, 2)
self.assertEqual(f.cache_info(), (1, 1, 1, 1))
self.assertEqual(f.cache_info(), (1, 1, 1, 1, False))

# Cache eviction: No use as an argument gives no additional call
self.assertEqual(f(6, 2), 20)
self.assertEqual(mock_int.__hash__.call_count, 2)
self.assertEqual(f.cache_info(), (1, 2, 1, 1))
self.assertEqual(f.cache_info(), (1, 2, 1, 1, False))

# Cache miss: One use as an argument gives one additional call
self.assertEqual(f(mock_int, 1), 16)
self.assertEqual(mock_int.__hash__.call_count, 3)
self.assertEqual(f.cache_info(), (1, 3, 1, 1))
self.assertEqual(f.cache_info(), (1, 3, 1, 1, False))

def test_lru_reentrancy_with_len(self):
# Test to make sure the LRU cache code isn't thrown-off by
Expand Down Expand Up @@ -1386,10 +1394,10 @@ def fib(n):
self.assertEqual([fib(n) for n in range(16)],
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610])
self.assertEqual(fib.cache_info(),
self.module._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16))
self.module._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16, typed=False))
fib.cache_clear()
self.assertEqual(fib.cache_info(),
self.module._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))
self.module._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0, typed=False))

def test_lru_with_maxsize_negative(self):
@self.module.lru_cache(maxsize=-10)
Expand All @@ -1398,7 +1406,7 @@ def eq(n):
for i in (0, 1):
self.assertEqual([eq(n) for n in range(150)], list(range(150)))
self.assertEqual(eq.cache_info(),
self.module._CacheInfo(hits=0, misses=300, maxsize=0, currsize=0))
self.module._CacheInfo(hits=0, misses=300, maxsize=0, currsize=0, typed=False))

def test_lru_with_exceptions(self):
# Verify that user_function exceptions get passed through without
Expand Down Expand Up @@ -1431,6 +1439,8 @@ def square(x):
self.assertEqual(type(square(x=3.0)), type(9.0))
self.assertEqual(square.cache_info().hits, 4)
self.assertEqual(square.cache_info().misses, 4)
_, _, _, _, typed = square.cache_info()
self.assertEqual(typed, True)

def test_lru_with_keyword_args(self):
@self.module.lru_cache()
Expand All @@ -1443,10 +1453,10 @@ def fib(n):
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
)
self.assertEqual(fib.cache_info(),
self.module._CacheInfo(hits=28, misses=16, maxsize=128, currsize=16))
self.module._CacheInfo(hits=28, misses=16, maxsize=128, currsize=16, typed=False))
fib.cache_clear()
self.assertEqual(fib.cache_info(),
self.module._CacheInfo(hits=0, misses=0, maxsize=128, currsize=0))
self.module._CacheInfo(hits=0, misses=0, maxsize=128, currsize=0, typed=False))

def test_lru_with_keyword_args_maxsize_none(self):
@self.module.lru_cache(maxsize=None)
Expand All @@ -1457,10 +1467,10 @@ def fib(n):
self.assertEqual([fib(n=number) for number in range(16)],
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610])
self.assertEqual(fib.cache_info(),
self.module._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16))
self.module._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16, typed=False))
fib.cache_clear()
self.assertEqual(fib.cache_info(),
self.module._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))
self.module._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0, typed=False))

def test_kwargs_order(self):
# PEP 468: Preserving Keyword Argument Order
Expand All @@ -1470,7 +1480,7 @@ def f(**kwargs):
self.assertEqual(f(a=1, b=2), [('a', 1), ('b', 2)])
self.assertEqual(f(b=2, a=1), [('b', 2), ('a', 1)])
self.assertEqual(f.cache_info(),
self.module._CacheInfo(hits=0, misses=2, maxsize=10, currsize=2))
self.module._CacheInfo(hits=0, misses=2, maxsize=10, currsize=2, typed=False))

def test_lru_cache_decoration(self):
def f(zomg: 'zomg_annotation'):
Expand All @@ -1485,7 +1495,7 @@ def test_lru_cache_threaded(self):
def orig(x, y):
return 3 * x + y
f = self.module.lru_cache(maxsize=n*m)(orig)
hits, misses, maxsize, currsize = f.cache_info()
hits, misses, maxsize, currsize, typed = f.cache_info()
self.assertEqual(currsize, 0)

start = threading.Event()
Expand All @@ -1508,7 +1518,7 @@ def clear():
with support.start_threads(threads):
start.set()

hits, misses, maxsize, currsize = f.cache_info()
hits, misses, maxsize, currsize, typed = f.cache_info()
if self.module is py_functools:
# XXX: Why can be not equal?
self.assertLessEqual(misses, n)
Expand Down Expand Up @@ -1538,7 +1548,7 @@ def test_lru_cache_threaded2(self):
def f(x):
pause.wait(10)
return 3 * x
self.assertEqual(f.cache_info(), (0, 0, m*n, 0))
self.assertEqual(f.cache_info(), (0, 0, m*n, 0, False))
def test():
for i in range(m):
start.wait(10)
Expand All @@ -1553,7 +1563,7 @@ def test():
start.reset()
stop.wait(10)
pause.reset()
self.assertEqual(f.cache_info(), (0, (i+1)*n, m*n, i+1))
self.assertEqual(f.cache_info(), (0, (i+1)*n, m*n, i+1, False))

def test_lru_cache_threaded3(self):
@self.module.lru_cache(maxsize=2)
Expand Down Expand Up @@ -1602,22 +1612,22 @@ def f(self, x):
a = X(5)
b = X(5)
c = X(7)
self.assertEqual(X.f.cache_info(), (0, 0, 2, 0))
self.assertEqual(X.f.cache_info(), (0, 0, 2, 0, False))

for x in 1, 2, 2, 3, 1, 1, 1, 2, 3, 3:
self.assertEqual(a.f(x), x*10 + 5)
self.assertEqual((a.f_cnt, b.f_cnt, c.f_cnt), (6, 0, 0))
self.assertEqual(X.f.cache_info(), (4, 6, 2, 2))
self.assertEqual(X.f.cache_info(), (4, 6, 2, 2, False))

for x in 1, 2, 1, 1, 1, 1, 3, 2, 2, 2:
self.assertEqual(b.f(x), x*10 + 5)
self.assertEqual((a.f_cnt, b.f_cnt, c.f_cnt), (6, 4, 0))
self.assertEqual(X.f.cache_info(), (10, 10, 2, 2))
self.assertEqual(X.f.cache_info(), (10, 10, 2, 2, False))

for x in 2, 1, 1, 1, 1, 2, 1, 3, 2, 1:
self.assertEqual(c.f(x), x*10 + 7)
self.assertEqual((a.f_cnt, b.f_cnt, c.f_cnt), (6, 4, 5))
self.assertEqual(X.f.cache_info(), (15, 15, 2, 2))
self.assertEqual(X.f.cache_info(), (15, 15, 2, 2, False))

self.assertEqual(a.f.cache_info(), X.f.cache_info())
self.assertEqual(b.f.cache_info(), X.f.cache_info())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
expose typed filed for lru_cache().cache_info()
8 changes: 4 additions & 4 deletions Modules/_functoolsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1248,13 +1248,13 @@ static PyObject *
lru_cache_cache_info(lru_cache_object *self, PyObject *unused)
{
if (self->maxsize == -1) {
return PyObject_CallFunction(self->cache_info_type, "nnOn",
return PyObject_CallFunction(self->cache_info_type, "nnOnn",
self->hits, self->misses, Py_None,
PyDict_GET_SIZE(self->cache));
PyDict_GET_SIZE(self->cache), self->typed);
}
return PyObject_CallFunction(self->cache_info_type, "nnnn",
return PyObject_CallFunction(self->cache_info_type, "nnnnn",
self->hits, self->misses, self->maxsize,
PyDict_GET_SIZE(self->cache));
PyDict_GET_SIZE(self->cache), self->typed);
}

static PyObject *
Expand Down