知道 python 缓存的 属性 是否在没有实际访问它的情况下被访问

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

有很多方法装饰器的例子可以将方法转换为缓存 属性。但有时,我想检查缓存是否为 "active",这意味着该属性已被访问并且缓存已被填充。

例如,如果我使用 rows 缓存来将 sql table 存储在 rows 中,我想计算 rows 的长度我的 table 基于缓存,如果它已被填充,但如果没有,则通过单独的 sql 调用。如何在不触发访问的情况下检查 rows 是否已被访问?

这是取自 David Beazley 的 "Python Cookbook") 的漂亮装饰器)我正在使用它来满足我缓存的 属性 需求。我已经对其进行了增强以启用我当前的 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

使用示例:

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

所以 hasattr 实际上触发了 len 方法调用,所以我不能使用它。无论如何,我不想使用它,因为我不是在要求属性的存在(key/reference),而是它的值的存在(即事先计算)。

给定 'my hack' 标记的行,我现在可以这样做:

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

但我相信还有比这更优雅的解决方案。也许 lazyprop 本身会 "coupled"...

仅供参考,通过 functools

,属性 缓存是 Python 3.8 标准库的一部分

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

使用此装饰器,您可以直接访问 class 的 __dict__ 属性以检查该值是否已缓存。

使用文档中的示例...

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)

然后进行测试...

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}