首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将惰性求值转换为装饰器(Python)

将惰性求值转换为装饰器(Python)
EN

Stack Overflow用户
提问于 2018-02-08 16:00:23
回答 2查看 773关注 0票数 2

我希望将许多样板代码转换为使用装饰器,但我在弄清楚如何做到这一点上遇到了困难。

我当前的代码如下所示:

代码语言:javascript
复制
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

这就像我想要的那样工作,但是很明显,写很多这样的代码是很烦人的。一个简短的例子似乎也是这样做的:

代码语言:javascript
复制
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)

然而,这看起来仍然非常冗长。最理想的情况是,我希望:

代码语言:javascript
复制
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‘做到这一点?如果我可以添加自己的docstring,或者至少复制函数的docstring,那就更好了。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-02-08 16:51:38

我能得到的最接近的结果如下:

代码语言:javascript
复制
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)

不幸的是,您仍然需要这一行额外的代码。问题是,该装饰器的外部两个函数不知道有关类或实例的任何信息,因此无法将结果绑定到该类或实例的成员。

如果您不关心内部名称是否与属性相同(这里我将方法名称作为基础),则不一定需要外部调用(带有名称):

代码语言:javascript
复制
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())生成一个随机名称。

票数 3
EN

Stack Overflow用户

发布于 2018-02-08 17:44:51

我对this answer by Graipher进行了修改,允许在重命名存储的_value和自定义函数调用的同时进行调用(这样您就不必对其进行封装)。

代码语言:javascript
复制
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)

要演示,请执行以下操作:

代码语言:javascript
复制
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)。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48680398

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档