Search code examples
pythonpython-descriptors

Knowing if a python cached property has been accessed without actually accessing it


There are many examples out there of method decorators that will transform a method into a cached property. Sometimes though, I'd like to check if the cache is "active", meaning that the attribute was accessed and the cache was filled.

For example, if I'm using a rows cached to store an sql table in a rows, I'd like to compute the length of my table based on the cache, if it's been filled, but through a separate sql call if not. How do I check if rows has been accessed without triggering its access?

Here's a nice decorator taken from David Beazley's "Python Cookbook") I'm using for my cached property needs. I've enhanced it to enable my current hack.

class lazyprop:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            value = self.func(instance)
            setattr(instance, self.func.__name__, value)
            setattr(instance, self.func.__name__ + '__cache_active', True)  # my hack
            return value

Example of use:

>>> class Test:
...     def __init__(self, a):
...         self.a = a
...     @lazyprop
...     def len(self):
...         print('generating "len"')
...         return len(self.a)
>>> t = Test([0, 1, 2])
>>> # See what happens if I ask if there is a 'len' attribute:
>>> hasattr(t, 'len')
generating "len"
3
>>> t.len
5

So the hasattr actually triggers the len method call, so I can't use that. Anyway, I wouldn't want to use it because I'm not asking for the existence of an attribute (the key/reference), but the existence of (i.e. prior computation of) it's value.

Given the line marked by 'my hack', I can now do this:

def has_active_cache(instance, attr):
    return getattr(instance, attr + '__cache_active', False)
>>> t = Test([0, 1, 2])
>>> print("Accessed:", has_active_cache(t, 'len'))
Accessed: False
>>> t.len
generating "len"
3
>>> print("Accessed:", has_active_cache(t, 'len'))
Accessed: True

But I believe there's a more graceful solution than this. Perhaps one that would come "coupled" with the lazyprop itself...


Solution

  • Just as an FYI, property caching is a part of the Python 3.8 standard library through functools

    https://docs.python.org/3/library/functools.html?highlight=s#functools.cached_property

    using this decorator, you can access the __dict__ attribute of your class directly to check if the value is cached.

    Using the example from the documentation...

    import statistics
    from functools import cached_property
    
    
    class DataSet:
        def __init__(self, sequence_of_numbers):
            self._data = sequence_of_numbers
    
        @cached_property
        def stdev(self):
            return statistics.stdev(self._data)
    
        @cached_property
        def variance(self):
            return statistics.variance(self._data)
    

    And then to test it out...

    ds = DataSet(range(1, 20))
    ds.stdev
    5.627314338711377
    ds.__dict__
    {'_data': range(1, 20), 'stdev': 5.627314338711377}
    ds.variance
    31.666666666666668
    ds.__dict__
    {'_data': range(1, 20), 'stdev': 5.627314338711377, 'variance': 31.666666666666668}