我希望将很多样板代码转换为使用装饰器,但我很难搞清楚如何去做。
我当前的代码看起来像这样:
import time # for demonstration
class C(object):
def large_function(self, optional_param=[]):
"""Large remote query that takes some time"""
time.sleep(3)
# usually run without optional_param
return ['val'] + optional_param
@property
def shorthand(self):
"""Docstr..."""
if not hasattr(self, "_shorthand"):
setattr(self, "_shorthand", self.large_function())
return self._shorthand
这就像我想要的那样,但显然很难写出其中的许多内容。一个似乎也是如此的简短例子:
import time # for demonstration
def lazy(self, name, func):
attr_name = "_" + name
if not hasattr(self, attr_name):
setattr(self, attr_name, func())
return getattr(self, attr_name)
class C(object):
def large_function(self, optional_param=[]):
"""Large remote query that takes some time"""
time.sleep(3)
# usually run without optional_param
return ['val'] + optional_param
@property
def shorthand(self):
"""Docstr..."""
return lazy(self, 'shorthand', self.large_function)
然而,这似乎仍然非常冗长。最理想的是,我想:
class C(object):
@add_lazy_property('shorthand')
def large_function(self, optional_param=[]):
"""Large remote query that takes some time"""
time.sleep(3)
# usually run without optional_param
return ['val'] + optional_param
c = C()
print(c.shorthand)
print(c.large_function(['add_param'])
不幸的是,我发现的懒惰装饰器完全掩盖了底层函数。我尝试了几个setattr()
的功能,但很快就让人感到困惑。有没有办法用一个装饰'add_lazy_property'做到这一点?如果我可以添加自己的文档字符串或者至少复制函数的文档字符串,那就可以获得奖励。
答案 0 :(得分:2)
我能得到的最接近的是:
def lazy_property(name):
internal_name = "_" + name
def method_decorator(method):
def wrapper(self, *args, **kwargs):
if not hasattr(self, internal_name):
setattr(self, internal_name, method(self, *args, **kwargs))
return getattr(self, internal_name)
return property(wrapper, doc=method.__doc__)
return method_decorator
class C(object):
def large_function(self, optional_param=[]):
"""Large remote query that takes some time"""
time.sleep(3)
# usually run without optional_param
return ['val'] + optional_param
shorthand = lazy_property("shorthand")(large_function)
不幸的是,你还需要一条额外的线。问题是该装饰器的外部两个函数对该类或实例一无所知,因此无法将结果绑定到该类或实例的成员。
如果您不关心内部名称与属性相同(此处我将方法名称作为基础),则不一定需要外部调用(带名称):
def lazy_property(method):
internal_name = "_" + method.__name__
def wrapper(self, *args, **kwargs):
if not hasattr(self, internal_name):
setattr(self, internal_name, method(self, *args, **kwargs))
return getattr(self, internal_name)
return property(wrapper, doc=method.__doc__)
class C(object):
def large_function(self, optional_param=[]):
"""Large remote query that takes some time"""
time.sleep(3)
# usually run without optional_param
return ['val'] + optional_param
shorthand = lazy_property(large_function)
或者,您可以使用str(uuid.uuid4())
生成随机名称。
答案 1 :(得分:1)
我对this answer by Graipher进行了修改,允许调用重命名存储的_value并自定义函数调用(所以你不必对它进行lambda包装)。
from collections import Callable
def lazy_property(method_or_name=None, *args, **kwargs):
"""Defines a lazy named property.
If method_or_name is Callable, immediately wraps it.
Otherwise, returns a wrapper with a custom name.
*args and **kwargs are passed onto the wrapped function."""
name = method_or_name
is_callable = isinstance(name, Callable) # Check if property is callable
def method_decorator(method): # Actual work
if not is_callable: internal_name = ("_%s" % name)
else: internal_name = "_" + method.__name__
def wrapper(self):
if not hasattr(self, internal_name):
setattr(self, internal_name, method(self, *args, **kwargs))
return getattr(self, internal_name)
return property(wrapper, doc=method.__doc__)
if is_callable: return method_decorator(name) # Allows lazy_property(method)
return method_decorator # Allows lazy_property("name")(method)
演示:
import time
class C(object):
def large_function(self, optional_param=[]):
"""Large remote query that takes some time"""
time.sleep(3)
# usually run without optional_param
return ['val'] + optional_param
short1 = lazy_property(large_function)
short2 = lazy_property("short2")(large_function)
short3 = lazy_property("short3", optional_param=["foo"])(large_function)
pass
c = C()
print(c.short1)
print(c.short2)
print(c.short3)
print(c.__dict__)
这是我目前需要的所有功能,而且看起来足够灵活。选择method_or_name
变量不太可能与任何kwargs
使用重合(而不仅仅是name
)。