【问题标题】:Convert lazy evaluation into decorator (Python)将惰性求值转换为装饰器(Python)
【发布时间】:2018-07-18 17:20:38
【问题描述】:

我希望将大量样板代码转换为使用装饰器,但我不知道该怎么做。

我当前的代码如下所示:

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'来做到这一点?如果我可以添加我自己的文档字符串或至少复制函数的文档字符串,则可以加分。

【问题讨论】:

    标签: python properties decorator python-decorators


    【解决方案1】:

    我能得到的最接近的是:

    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()) 生成一个随机名称。

    【讨论】:

    • 这个答案对我来说已经足够好了。我将添加我自己的实现作为答案(基于你的 - 只是在上面加了一点糖),但接受你的答案。我完全忘记了您可以使用文档字符串信息调用属性 - 不幸的是,这似乎没有在 Visual Studio 中传递,所以我不得不这样做。
    • @AnatolyMakarevich 如果你想放弃它作为一个属性,你也可以使用functools.wraps,也许 Visual Studio 可以识别这一点。你必须做c.shorthand(),不过......
    【解决方案2】:

    我对 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)。

    【讨论】:

      猜你喜欢
      • 2011-07-14
      • 1970-01-01
      • 2013-08-19
      • 1970-01-01
      • 2011-03-18
      • 2013-12-30
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多