【问题标题】:Difference between @cached_property and @lru_cache decorator@cached_property 和 @lru_cache 装饰器之间的区别
【发布时间】:2018-09-01 08:57:36
【问题描述】:

我是 Django 新手。如果有人能分辨出 Django 中 @cached_property 和 @lru_cache 装饰器之间的区别,那将非常有帮助。 我什么时候应该在 Django 中使用 which 装饰器。用例真的很有帮助。 谢谢。

【问题讨论】:

标签: python django


【解决方案1】:

首先,lru_cache 是 Python 语言本身在 3.4 版本中提供的装饰器; cached_property 是 Django 提供多年的装饰器,但在 2019 年 10 月的 3.8 版本中才被添加到 Python 语言中。话虽如此,它们是相似的。

lru_cache 在函数式编程中特别有用。它的作用是使用一组参数保存函数调用的结果。当使用lru_cache 修饰的函数以相同的参数多次调用时,装饰器将只返回函数结果的缓存结果。这采用了一种称为dynamic programming 的编程方法,更具体地说,是memoization。使用这些方法,您可以显着加快重复调用计算量大的函数的代码。

Python 还提供了另一个类似的装饰器lfu_cache。这两个装饰器都完成了记忆化,但是 replacement policies 不同。 lru_cache(最近最少使用)将填充它的缓存,并且必须在下一个修饰函数调用期间踢出一些东西。此替换策略规定最近最少使用的条目将被新数据替换。 lfu_cache(最不常用)指示根据使用最少的条目进行替换。

cached_property 类似于lru_cache,因为它缓存了昂贵的函数调用的结果。这里唯一的区别是它只能用于方法,这意味着函数属于一个对象。此外,它们只能用于除self 之外没有其他参数的方法。您会特别希望在 django 开发期间将其用于访问数据库的类上的方法。 Django docs 提到了它在具有属性方法friends 的模型类上的用法。该方法可能会访问数据库以收集一组与Person 实例相关的人。因为对数据库的调用很昂贵,所以我们希望缓存该结果以供以后使用。

【讨论】:

【解决方案2】:
  1. 主要区别在于lru_cache 将保持缓存中的对象处于活动状态,这可能会导致内存泄漏,尤其是在应用 lru_cache 的实例很大时(请参阅:https://bugs.python.org/issue19859
class A:

  @property
  @functools.lru_cache(maxsize=None)
  def x(self):
    return 123

for _ in range(100):
  A().x  # Call lru_cache on 100 different `A` instances

# The instances of `A()` are never garbage-collected:
assert A.x.fget.cache_info().currsize == 100

使用cached_property,没有缓存,所以没有内存泄漏。

class B:

  @functools.cached_property
  def x(self):
    return 123

b = B()
print(vars(b))  # {}
b.x
print(vars(b))  # {'x': 123}
del b  # b is garbage-collected
  1. 另一个区别是@property 是只读的,而@cached_property 不是。 cache_property 允许写入属性 Refer Python docs
A().x = 123
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
B().x = 123  # Works

这是因为 @cached_property 正在替换属性,所以对 b.x 的第二次调用绕过了 B.x.get 描述符调用。

  1. 另一个在大多数情况下可能无关紧要的区别是,如果您多次访问同一个属性,cached_property 的性能会更高,而lru_cache 有函数调用和属性查找的开销。请注意,只有大数字才能看到差异。
[A().x for _ in range(10_000)]
[B().x for _ in range(10_000)]

a = A()
b = B()

print(timeit.timeit(lambda: a.x, number=1_000_000))  # ~0.83
print(timeit.timeit(lambda: b.x, number=1_000_000))  # ~0.57

【讨论】:

  • 感谢您提到@cached_property 属性不是只读的,这是一个重要的细节,奇怪的是在the python docs 中没有提到。如果你问我有点误导......
  • @lunguini python 文档明确提到cached_propery 允许写入属性。摘自python docs cached_property() 的机制与 property() 有些不同。除非定义了 setter,否则常规属性会阻止属性写入。相比之下,cached_property 允许写入。
  • 你是对的,文档现在明确讨论了这一点。 in 3.8(第一个包含 @cached_property 的版本)他们没有
【解决方案3】:

它们有不同的用途。

lru_cache 保存最近最少的使用 - 您应该指定 maxsize 以区分您可以保存多少次函数计算。一旦超过这个数字,“最旧”的结果就会被丢弃,新的结果会被保存。

cached_property 只是计算结果并保存。它不像 lru_cache 那样接受参数(您可以将其视为 maxsize = 1 且不带参数的对象类型上的 lru_cache)。

【讨论】:

    猜你喜欢
    • 2015-12-12
    • 2020-12-10
    • 2011-06-06
    • 2018-11-22
    • 2017-04-21
    • 1970-01-01
    • 2016-03-16
    • 1970-01-01
    • 2016-06-12
    相关资源
    最近更新 更多