将延迟评估转换为装饰器(Python)

时间:2018-02-08 08:00:23

标签: python properties decorator python-decorators

我希望将很多样板代码转换为使用装饰器,但我很难搞清楚如何去做。

我当前的代码看起来像这样:

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'做到这一点?如果我可以添加自己的文档字符串或者至少复制函数的文档字符串,那就可以获得奖励。

2 个答案:

答案 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)。