Clearing lru_cache of certain methods when an attribute of the class is updated?

Viewed 1399

I have an object with a method/property multiplier. This method is called many times in my program, so I've decided to use lru_cache() on it to improve the execution speed. As expected, it is much faster:

The following code shows the problem:

from functools import lru_cache

class MyClass(object):
    def __init__(self):
        self.current_contract = 201706
        self.futures = {201706: {'multiplier': 1000},
                        201712: {'multiplier': 25}}

    @property
    @lru_cache()
    def multiplier(self):
        return self.futures[self.current_contract]['multiplier']

CF = MyClass()
assert CF.multiplier == 1000

CF.current_contract = 201712
assert CF.multiplier == 25

The 2nd assert fails, because the cached value is 1000 as lru_cache() is unaware that the underlying attribute current_contract was changed.

Is there a way to clear the cache when self.current_contract is updated?

Thanks!

2 Answers

Short Answer

Don't clear the cache when self.current_contract is updated. That is working against the cache and throws away information.

Instead, just add methods for __eq__ and __hash__. That will teach the cache (or any other mapping) which attributes are important for influencing the result.

Worked out example

Here we add __eq__ and __hash__ to your code. That tells the cache (or any other mapping) that current_contract is the relevant independent variable:

from functools import lru_cache

class MyClass(object):
    def __init__(self):
        self.current_contract = 201706
        self.futures = {201706: {'multiplier': 1000},
                        201712: {'multiplier': 25}}

    def __hash__(self):
        return hash(self.current_contract)

    def __eq__(self, other):
        return self.current_contract == other.current_contract

    @property
    @lru_cache()
    def multiplier(self):
        return self.futures[self.current_contract]['multiplier']

An immediate advantage is that as you switch between contract numbers, previous results are kept in the cache. Try switching between 201706 and 201712 a hundred times and you will get 98 cache hits and 2 cache misses:

cf = MyClass()
for i in range(50):
    cf.current_contract = 201712
    assert cf.multiplier == 25
    cf.current_contract = 201706 
    assert cf.multiplier == 1000
print(vars(MyClass)['multiplier'].fget.cache_info())

This prints:

CacheInfo(hits=98, misses=2, maxsize=128, currsize=2)
Related