我正在尝试为类方法@cachedproperty
编写一个装饰器。我希望它的行为,以便在首次调用该方法时,该方法将替换为其返回值。我还希望它的行为类似于@property
,因此不需要显式调用它。基本上,它应该与@property
无法区分,除了它更快,因为它只计算一次值然后存储它。我的想法是,这不会减慢实例化速度,例如在__init__
中定义它。这就是我想要这样做的原因。
首先,我尝试覆盖fget
的{{1}}方法,但它是只读的。
接下来,我想我会尝试实现一个需要第一次调用的装饰器,然后缓存值。这不是我永远不需要调用的属性类型装饰器的最终目标,但我认为这将是一个更容易解决的问题。换句话说,对于稍微简单的问题,这是一个无法解决的问题。
我试过了:
property
然而,这似乎不起作用。我测试了这个:
def cachedproperty(func):
""" Used on methods to convert them to methods that replace themselves
with their return value once they are called. """
def cache(*args):
self = args[0] # Reference to the class who owns the method
funcname = inspect.stack()[0][3] # Name of the function, so that it can be overridden.
setattr(self, funcname, func()) # Replace the function with its value
return func() # Return the result of the function
return cache
但是我得到一个关于类没有将自己传递给方法的错误:
>>> class Test:
... @cachedproperty
... def test(self):
... print "Execute"
... return "Return"
...
>>> Test.test
<unbound method Test.cache>
>>> Test.test()
此时,我和我对深度Python方法的有限知识非常困惑,我不知道我的代码出错了或者如何修复它。 (我之前从未尝试过写装饰师)
如何编写一个装饰器,它将在第一次访问时返回调用类方法的结果(如Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method cache() must be called with Test instance as first argument (got nothing instead)
那样),并替换为所有后续查询的缓存值?
我希望这个问题不会太混乱,我试着尽可能地解释它。
答案 0 :(得分:15)
如果您不介意其他解决方案,我建议lru_cache
例如
from functools import lru_cache
class Test:
@property
@lru_cache(maxsize=None)
def calc(self):
print("Calculating")
return 1
预期输出
In [2]: t = Test()
In [3]: t.calc
Calculating
Out[3]: 1
In [4]: t.calc
Out[4]: 1
答案 1 :(得分:8)
首先应该Test
实例化
test = Test()
其次,不需要inspect
因为我们可以从func.__name__
获取属性名称
第三,我们返回property(cache)
让python做所有的魔法。
def cachedproperty(func):
" Used on methods to convert them to methods that replace themselves\
with their return value once they are called. "
def cache(*args):
self = args[0] # Reference to the class who owns the method
funcname = func.__name__
ret_value = func(self)
setattr(self, funcname, ret_value) # Replace the function with its value
return ret_value # Return the result of the function
return property(cache)
class Test:
@cachedproperty
def test(self):
print "Execute"
return "Return"
>>> test = Test()
>>> test.test
Execute
'Return'
>>> test.test
'Return'
>>>
“”“
答案 2 :(得分:3)
我认为你最好使用自定义描述符,因为这正是描述符的用途。像这样:
class CachedProperty:
def __init__(self, name, get_the_value):
self.name = name
self.get_the_value = get_the_value
def __get__(self, obj, typ):
name = self.name
while True:
try:
return getattr(obj, name)
except AttributeError:
get_the_value = self.get_the_value
try:
# get_the_value can be a string which is the name of an obj method
value = getattr(obj, get_the_value)()
except AttributeError:
# or it can be another external function
value = get_the_value()
setattr(obj, name, value)
continue
break
class Mine:
cached_property = CachedProperty("_cached_property ", get_cached_property_value)
# OR:
class Mine:
cached_property = CachedProperty("_cached_property", "get_cached_property_value")
def get_cached_property_value(self):
return "the_value"
编辑:顺便说一句,你甚至不需要自定义描述符。您可以将值缓存在属性函数中。例如:
@property
def test(self):
while True:
try:
return self._test
except AttributeError:
self._test = get_initial_value()
这就是它的全部。
然而,许多人会认为这有点滥用property
,并且是一种意想不到的使用方式。而意外通常意味着你应该采用另一种更明确的方式。自定义CachedProperty
描述符非常明确,因此我更倾向于使用property
方法,尽管它需要更多代码。
答案 3 :(得分:2)
您可以使用以下内容:
def cached(timeout=None):
def decorator(func):
def wrapper(self, *args, **kwargs):
value = None
key = '_'.join([type(self).__name__, str(self.id) if hasattr(self, 'id') else '', func.__name__])
if settings.CACHING_ENABLED:
value = cache.get(key)
if value is None:
value = func(self, *args, **kwargs)
if settings.CACHING_ENABLED:
# if timeout=None Django cache reads a global value from settings
cache.set(key, value, timeout=timeout)
return value
return wrapper
return decorator
当添加到缓存字典时,它会根据约定class_id_function
生成密钥,以防您缓存实体,并且该属性可能会为每个实体返回不同的值。
它还会检查设置键CACHING_ENABLED
,以防您在进行基准测试时暂时将其关闭。
但它没有封装标准的property
装饰器,所以你仍然应该像函数一样调用它,或者你可以像这样使用它(为什么只将它限制为属性):
@cached
@property
def total_sales(self):
# Some calculations here...
pass
另外值得注意的是,如果你是从懒惰的外键关系缓存结果,有时候根据你的数据,在执行select查询并获取所有内容时简单地运行聚合函数会更快一次,而不是访问结果集中每条记录的缓存。因此,使用django-debug-toolbar
之类的工具为您的框架比较在您的方案中表现最佳的工具。
答案 4 :(得分:1)
Django这个装饰器的版本正是你所描述的并且很简单,所以除了我的评论之外我还会把它复制到这里:
class cached_property(object):
"""
Decorator that converts a method with a single self argument into a
property cached on the instance.
Optional ``name`` argument allows you to make cached properties of other
methods. (e.g. url = cached_property(get_absolute_url, name='url') )
"""
def __init__(self, func, name=None):
self.func = func
self.__doc__ = getattr(func, '__doc__')
self.name = name or func.__name__
def __get__(self, instance, type=None):
if instance is None:
return self
res = instance.__dict__[self.name] = self.func(instance)
return res
(source)。
正如您所看到的,它使用func.name来确定函数的名称(不需要使用inspect.stack),并通过变更instance.__dict__
将结果替换为方法。因此,后续的“调用”只是一个属性查找,不需要任何缓存,等等。
答案 5 :(得分:0)
@functools.lru_cache()
def func(....):
....