为什么使用Python装饰程序而不是闭包?

为什么使用Python装饰程序而不是闭包?,python,decorator,Python,Decorator,我还没有掌握Python中的装饰程序 我已经开始使用很多闭包来做一些事情,比如在编码中自定义函数和类 例如 就我所见,decorator只是做类似事情的不同语法 而不是 def pp(n) : print "%s," % n.val printTree = makeRunner(pp) 我会写: @makeRunner def printTree(n) : print "%s," % n.val 这就是装饰师的全部吗?还是我错过了一个根本性的区别 您的示例是真正的代码,还是只是示例 如果它们

我还没有掌握Python中的装饰程序

我已经开始使用很多闭包来做一些事情,比如在编码中自定义函数和类

例如

就我所见,decorator只是做类似事情的不同语法

而不是

def pp(n) : print "%s," % n.val
printTree = makeRunner(pp)
我会写:

@makeRunner
def printTree(n) : print "%s," % n.val

这就是装饰师的全部吗?还是我错过了一个根本性的区别

您的示例是真正的代码,还是只是示例

如果它们是真正的代码,我认为您过度使用装饰程序,可能是因为您的背景(即您习惯于其他编程语言)

第一阶段:避免装饰 此运行方法淘汰makeRunner。你的例子是:

def pp(n): print "%s," % n.val
run(tree, pp)
然而,这完全忽略了生成器,所以

第二阶段:使用发电机 你的榜样依然存在

def pp(n): print "%s," % n.val
run(tree, pp)
请注意,特殊方法
\uuuu iter\uuu
允许我们在rootnode:构造中为节点使用
。如果您不喜欢,只需将
\uu iter\uuu
方法重命名为例如
walker
,并将
运行
循环更改为:
for rootnode.walker()中的节点:

显然,
run
函数可以是
类节点的方法

如您所见,我建议您直接使用
run(tree,func)
,而不是将它们绑定到名称
printree
,但您可以在装饰器中使用它们,也可以使用
functools。分部
函数:

printTree= functools.partial(run, func=pp)
从那时起,你会

printTree(tree)

诚然,从句法上讲,装饰师只是“糖”,但这并不是思考它们的最佳方式

装饰器允许您将功能编织到现有代码中,而无需实际修改它。它们允许你以一种声明性的方式来做

这允许您使用装饰器进行面向方面编程(AOP)。所以,当你有一个横切的关注点,你想要封装在一个地方时,你想要使用一个装饰器

最典型的例子可能是日志记录,您希望记录函数的入口或出口,或两者。使用decorator相当于向连接点(在方法进入或退出期间)应用建议(记录此!)


方法装饰是一个类似于OOP或列表理解的概念。正如您所指出的,它并不总是合适的,可能会被过度使用。但是在正确的地方,它可以使代码更加模块化和解耦。

继Dutch Master的AOP参考之后,您会发现当您开始添加参数来修改修饰函数/方法的行为时,使用修饰器变得特别有用,阅读上面的函数定义要容易得多

在我记得的一个项目中,我们需要监督大量芹菜任务,因此我们想出了使用装饰器根据需要进行插入和调整的想法,这类似于:

class tracked_with(object):
    """
    Method decorator used to track the results of celery tasks.
    """
    def __init__(self, model, unique=False, id_attr='results_id',
                 log_error=False, raise_error=False):
        self.model = model
        self.unique = unique
        self.id_attr = id_attr
        self.log_error = log_error
        self.raise_error = raise_error

    def __call__(self, fn):

        def wrapped(*args, **kwargs):
            # Unique passed by parameter has priority above the decorator def
            unique = kwargs.get('unique', None)
            if unique is not None:
                self.unique = unique

            if self.unique:
                caller = args[0]
                pending = self.model.objects.filter(
                    state=self.model.Running,
                    task_type=caller.__class__.__name__
                )
                if pending.exists():
                    raise AssertionError('Another {} task is already running'
                                         ''.format(caller.__class__.__name__))

            results_id = kwargs.get(self.id_attr)
            try:
                result = fn(*args, **kwargs)

            except Retry:
                # Retry must always be raised to retry a task
                raise

            except Exception as e:
                # Error, update stats, log/raise/return depending on values
                if results_id:
                    self.model.update_stats(results_id, error=e)
                if self.log_error:
                    logger.error(e)
                if self.raise_error:
                    raise
                else:
                    return e

            else:
                # No error, save results in refresh object and return
                if results_id:
                    self.model.update_stats(results_id, **result)
                return result

        return wrapped
然后,我们简单地用每种情况所需的参数修饰任务上的
run
方法,如:

class SomeTask(Task):

    @tracked_with(RefreshResults, unique=True, log_error=False)
    def run(self, *args, **kwargs)...

然后改变任务的行为(或者完全删除跟踪)意味着调整一个参数,或者注释掉修饰的行。非常容易实现,但更重要的是,检查时非常容易理解。

装饰器一般来说是围绕另一个对象的函数或类,用于扩展或装饰对象。装饰器支持与包装的函数或对象相同的接口,因此接收者甚至不知道对象已装饰

闭包是一个匿名函数,它引用其范围之外的参数或其他变量

所以基本上,装饰器使用闭包,而不是替换它们

def increment(x):
    return x + 1

def double_increment(func):
    def wrapper(x):
        print 'decorator executed'
        r = func(x)   # --> func is saved in __closure__
        y = r * 2
        return r, y
    return wrapper

@double_increment
def increment(x):
    return x + 1

>>> increment(2)
decorator executed
(3, 6)

>>> increment.__closure__
(<cell at 0x02C7DC50: function object at 0x02C85DB0>,)

>>> increment.__closure__[0].cell_contents 
<function increment at 0x02C85DB0>
def增量(x):
返回x+1
def双_增量(func):
def包装(x):
打印“decorator已执行”
r=func(x)#-->func保存在_闭包中__
y=r*2
返回r,y
返回包装器
@双倍增量
def增量(x):
返回x+1
>>>增量(2)
装修工
(3, 6)
>>>增量.\uuu闭包__
(,)
>>>增量.\uuuu闭包\uuuu[0]。单元格内容

因此,装饰程序使用闭包保存原始函数

而使用迭代器通常很好,我发现您的重写更糟糕。“for item in child:#recurse”的作用非常不明显,即使有注释也是如此。最初的代码使用的只是函数,更明显,更小;尽管基于我过去做过的类似事情。谢谢你的选择。有趣。我以前肯定用过stage 1表单,但我想我从来没有用iter运行过tree。实际上,看看你的最后一点,为什么你更喜欢functools.partial而不是decorator版本?(即,每种方法的优缺点是什么?@interstar:我修正了我最后一点的措辞;我只是提供了一种装饰的替代方法。@James Antill:迭代器的另一个名称是否会使它更明显,如child.walk_subtree()中的项的
?我建议使用迭代器,因为它使代码的其余部分比传递函数对象更容易,特别是如果你想在遍历树时保持上下文的话;虽然解释很好,很简洁
class tracked_with(object):
    """
    Method decorator used to track the results of celery tasks.
    """
    def __init__(self, model, unique=False, id_attr='results_id',
                 log_error=False, raise_error=False):
        self.model = model
        self.unique = unique
        self.id_attr = id_attr
        self.log_error = log_error
        self.raise_error = raise_error

    def __call__(self, fn):

        def wrapped(*args, **kwargs):
            # Unique passed by parameter has priority above the decorator def
            unique = kwargs.get('unique', None)
            if unique is not None:
                self.unique = unique

            if self.unique:
                caller = args[0]
                pending = self.model.objects.filter(
                    state=self.model.Running,
                    task_type=caller.__class__.__name__
                )
                if pending.exists():
                    raise AssertionError('Another {} task is already running'
                                         ''.format(caller.__class__.__name__))

            results_id = kwargs.get(self.id_attr)
            try:
                result = fn(*args, **kwargs)

            except Retry:
                # Retry must always be raised to retry a task
                raise

            except Exception as e:
                # Error, update stats, log/raise/return depending on values
                if results_id:
                    self.model.update_stats(results_id, error=e)
                if self.log_error:
                    logger.error(e)
                if self.raise_error:
                    raise
                else:
                    return e

            else:
                # No error, save results in refresh object and return
                if results_id:
                    self.model.update_stats(results_id, **result)
                return result

        return wrapped
class SomeTask(Task):

    @tracked_with(RefreshResults, unique=True, log_error=False)
    def run(self, *args, **kwargs)...
def increment(x):
    return x + 1

def double_increment(func):
    def wrapper(x):
        print 'decorator executed'
        r = func(x)   # --> func is saved in __closure__
        y = r * 2
        return r, y
    return wrapper

@double_increment
def increment(x):
    return x + 1

>>> increment(2)
decorator executed
(3, 6)

>>> increment.__closure__
(<cell at 0x02C7DC50: function object at 0x02C85DB0>,)

>>> increment.__closure__[0].cell_contents 
<function increment at 0x02C85DB0>