【问题标题】:Clearing lru_cache of certain methods when an attribute of the class is updated?更新类的属性时清除某些方法的 lru_cache?
【发布时间】:2017-12-30 03:50:26
【问题描述】:

我有一个带有方法/属性multiplier 的对象。这个方法在我的程序中被调用了很多次,所以我决定在它上面使用lru_cache()来提高执行速度。正如预期的那样,它要快得多:

以下代码显示了问题:

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

第二个 assert 失败,因为缓存值为 1000,因为 lru_cache() 不知道基础属性 current_contract 已更改。

有没有办法在 self.current_contract 更新时清除缓存?

谢谢!

【问题讨论】:

    标签: python caching synchronization functools lru


    【解决方案1】:

    是的,很简单:将current_contract 设为读/写属性并清除属性设置器中的缓存:

    from functools import lru_cache
    
    class MyClass(object):
        def __init__(self):
            self.futures = {201706: {'multiplier': 1000},
                            201712: {'multiplier': 25}}
            self.current_contract = 201706
    
        @property
        def current_contract(self):
            return self._current_contract
    
        @current_contract.setter
        def current_contract(self, value):
            self._current_contract = value
            type(self).multiplier.fget.cache_clear()
    
        @property
        @lru_cache()
        def multiplier(self):
            return self.futures[self.current_contract]['multiplier']
    

    注意:我假设您的实际用例涉及昂贵的计算,而不仅仅是 dict 查找 - 否则 lru_cache 可能有点矫枉过正;)

    【讨论】:

    • 实际上它只是一个字典查找,但它在我的程序中被调用了数十万次,并且使用 lru_cache 产生了很大的不同。我将使用新代码再次对其进行测试。非常感谢您的帮助!它解决了问题。
    • 如果您有这样的优化需求,您可能希望在multiplier 中使用self._current_contract 而不是self.current_contract(以避免属性调用/方法调用/属性解析开销),并且可能只需将 multiplier 设置为在 current_contract setter 中设置的普通属性(请注意,我没有进行任何基准测试,因此您可能想先 timeit 找出哪个解决方案确实是最快的)
    • 这就是我所做的,将很快测试它。感谢您的建议。
    • @brunodesthuilliers 你能告诉我为什么你必须使用type(self).multiplier.fget.cache_clear()而不是self.multiplier.fget.cache_clear()吗?谢谢
    • 因为否则你会触发属性机制。您可以阅读有关描述符(支持计算属性的一般机制)的官方文档以获取详细信息。
    【解决方案2】:

    简答

    self.current_contract 更新时不清除缓存。这对缓存起作用并丢弃信息。

    相反,只需为__eq____hash__ 添加方法。这将告诉缓存(或任何其他映射)哪些属性对影响结果很重要。

    制定的例子

    在这里,我们将__eq____hash__ 添加到您的代码中。这告诉缓存(或任何其他映射) current_contract 是相关的自变量:

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

    一个直接的优势是,当您在合约编号之间切换时,之前的结果会保存在缓存中。尝试在 201706 和 201712 之间切换一百次,您将获得 98 次缓存命中和 2 次缓存未命中:

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

    打印出来:

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

    【讨论】:

      猜你喜欢
      • 2013-12-05
      • 2023-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多