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...
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}