简体   繁体   中英

How do I clear the cache from @cached_property decorator?

I have an function called "value" that makes heavy calculation...

The result of the function is always the same if the dataset is not changed for the identifier.

Once the dataset is changed for some identifier, I want to clear the cache, and let the function calculate it again.

You can better understand me by looking at this code:

from functools import cached_property


class Test:
    identifiers = {}
    dataset = an empty object of dataset type

    def __init__(self, identifier, ...)
        self.identifier = identifier
        ...
        Test.identifiers[identifier] = self

    ...

    @cached_property
    def value(self):
        result = None
        # heavy calculate based on dataset
        return result

    @classmethod
    def get(cls, identifier):
        if identifier in cls.identifiers:
            return cls.identifiers[identifier]
        else:
            return cls(identifier, ...)

    @classmethod
    def update(cls, dataset):
        for block in dataset:
            # assume there is block['identifier'] in each block
            # here i want to clear the cache of value() function
            instance = cls.get(block['identifier'])
            # clear @cached_property of instance
            cls.dataset.append(block)

As you can read in the CPython source , the value for a cached_property in Python 3.8 is stored in an instance variable of the same name. This is not documented, so it may be an implementation detail that you should not rely upon.

But if you just want to get it done without regards to compatibility, you can remove the cache with del instance.value . And who knows, maybe the current behavior will be documented in the future, so it will be safe to use it in any version or interpreter implementation.

(Aditional to the prev answer)

In case that you modify an object and you need to reload the @cached_property (because the object has been mutated) you can delete the properties that are already cached on the self.__dict__ dictionary (that's where the properties are cached)

from functools import cached_property

class Test:
    datalist: List[int]

    @cached_property
    def value(self):
        result = None
        # heavy calculate based on datalist
        return result

    def add_element(self, new:int)-> None:
        # restore cache if calculated 
        self.__dict__.pop('value', None) # this will delete the cached val   
        self.datalist.append(new)

I offer an alternative approach, which might be useful in some cases. If the type of the dataset you need to do the computation on is hashable, you can make use of the regular functools.cache or lru_cache decorator, applied to a static method that takes the dataset as input.

Here is an example of what I mean:

from functools import lru_cache

class MyClass():

    def __init__(self, data):
        self.data = data

    @property    
    def slow_attribute(self):
        return self._slow_attribute(self.data)
        
    @staticmethod
    @lru_cache
    def _slow_attribute(data):

        # long computation, using data,
        # here is just an example

        return sum(data)

Here there is no need to concern yourself with when to clear the cache: if the underlying dataset changes, the staticmethod automatically knows it cannot use the cached value anymore.

This has the additional perk that, if the dataset were to be restored to a previously-used state, the lookup may still be able to use a cached value.

Here is a demo of the code above working:

from time import perf_counter_ns

def print_time_and_value_of_computation(c):
    
    t1 = perf_counter_ns()    
    val = c.slow_attribute
    t2 = perf_counter_ns()

    print(f'Time taken: {(t2 - t1)/1000} microseconds')
    print(f'Value: {val}')


c = MyClass(range(10_000))

print_time_and_value_of_computation(c)
print_time_and_value_of_computation(c)

print('Changing the dataset!')
c.data = range(20_000)

print_time_and_value_of_computation(c)
print_time_and_value_of_computation(c)

print('Going back to the original dataset!')
c.data = range(10_000)

print_time_and_value_of_computation(c)

which returns:

Time taken: 162.074 microseconds
Value: 49995000
Time taken: 2.152 microseconds
Value: 49995000
Changing the dataset!
Time taken: 264.121 microseconds
Value: 199990000
Time taken: 1.989 microseconds
Value: 199990000
Going back to the original dataset!
Time taken: 1.144 microseconds
Value: 49995000

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM