Reputation: 13107
I'm trying to use functools.lru_cache
to cache the result of an inner function, but the cache doesn't seem to work as expected.
I have a function that performs some logic and then calls a rather expensive function. I'd like to cache the result of the expensive function call and though I'd just apply the lru_cache
to an inner function. Unfortunately the behavior isn't as expected - the expensive function gets called every time even though the arguments to the inner function are identical.
I've created a (simplified) test case to show the behavior:
import unittest
from functools import lru_cache
from unittest.mock import patch
def expensive_function(parameter: str) -> str:
return parameter
def partially_cached(some_parameter: str) -> str:
@lru_cache
def inner_function(parameter: str):
return expensive_function(parameter)
result = inner_function(some_parameter)
print(inner_function.cache_info())
return result
class CacheTestCase(unittest.TestCase):
def test_partially_cached(self):
with patch(self.__module__ + ".expensive_function") as expensive_mock:
expensive_mock.return_value = "a"
self.assertEqual(partially_cached("a"), "a")
self.assertEqual(partially_cached("a"), "a")
# If the cache works, I expect the expensive function
# to be called just once for the same parameter
expensive_mock.assert_called_once()
if __name__ == "__main__":
unittest.main()
(In this case I wouldn't need the inner function, but as I said - it's simplified)
Unfortunately the test fails
python3 /scratch/test_cache.py
CacheInfo(hits=0, misses=1, maxsize=128, currsize=1)
CacheInfo(hits=0, misses=1, maxsize=128, currsize=1)
F
======================================================================
FAIL: test_partially_cached (__main__.CacheTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
[...]
AssertionError: Expected 'expensive_function' to have been called once. Called 2 times.
Calls: [call('a'), call('a')].
----------------------------------------------------------------------
Ran 1 test in 0.004s
FAILED (failures=1)
I probably have a misunderstanding concerning either inner functions or the lru_cache, but I'm not sure which it is - I appreciate all the help I can get.
Upvotes: 12
Views: 3627
Reputation: 4964
It does not work as intended because the inner_function
gets redefined each time partially_cached
is called, and so is the cached version. So each cached version gets called only once.
See memoizing-decorator-keeping-stored-values
Additionally, if you mock a decorated function you need to apply the decorator again.
See how-to-mock-a-decorated-function
If you decorate at the outer level, or decorate another extracted to outer level function, it will work, if you don't break it with mocking.
Upvotes: 9