18

我仍然对 Python 中的装饰器一无所知。

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

例如。

class Node :
    def __init__(self,val,children) :
        self.val = val
        self.children = children

def makeRunner(f) :
    def run(node) :
        f(node)
        for x in node.children :
            run(x)
    return run

tree=Node(1,[Node(2,[]),Node(3,[Node(4,[]),Node(5,[])])])

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

据我所知,装饰器只是做类似事情的不同语法。

代替

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

我会写:

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

这就是装饰者的全部吗?还是我错过了根本的区别?

4

4 回答 4

14

虽然从语法上讲,装饰器只是“糖”,但这并不是考虑它们的最佳方式。

装饰器允许您将功能编织到现有代码中,而无需实际修改它。它们允许您以声明性的方式进行操作。

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

典型的示例可能是日志记录,您希望在其中记录函数的进入或退出,或两者兼而有之。使用装饰器相当于将建议(记录这个!)应用于连接点(在方法进入或退出期间)。

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

于 2008-12-02T21:03:22.920 回答
9

您的示例是真实代码,还是只是示例?

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

第 1 阶段:避免使用装饰器

def run(rootnode, func):
    def _run(node): # recursive internal function
        func(node)
        for x in node.children:
            _run(x) # recurse
    _run(rootnode) # initial run

此运行方法已淘汰 makeRunner。你的例子变成:

def pp(n): print "%s," % n.val
run(tree, pp)

但是,这完全忽略了生成器,所以……</p>

第 2 阶段:使用生成器

class Node :
    def __init__(self,val,children) :
        self.val = val
        self.children = children

    def __iter__(self): # recursive
        yield self
        for child in self.children:
            for item in child: # recurse
                yield item

def run(rootnode, func):
    for node in rootnode:
        func(node)

你的例子仍然存在

def pp(n): print "%s," % n.val
run(tree, pp)

请注意,特殊方法__iter__允许我们使用for node in rootnode:构造。如果您不喜欢它,只需将__iter__方法重命名为 eg walker,并将run循环更改为:for node in rootnode.walker():
显然,该run函数可以是一个方法class Node

如您所见,我建议您直接使用run(tree, func)而不是将它们绑定到 name printTree,但您可以在装饰器中使用它们,或者您可以使用functools.partial函数:

printTree= functools.partial(run, func=pp)

从那时起,你就会

printTree(tree)
于 2008-10-19T00:34:16.660 回答
5

装饰器,在一般意义上,是环绕另一个对象、扩展或装饰该对象的函数或类。装饰器支持与包装函数或对象相同的接口,因此接收者甚至不知道对象已被装饰。

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

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

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>

所以装饰器用闭包保存了原来的函数。

于 2017-08-24T15:50:40.133 回答
2

跟进 Dutch Master 的 AOP 参考资料,您会发现当您开始添加参数以修改被修饰函数/方法的行为时,使用装饰器变得特别有用,并且阅读函数定义上方的内容要容易得多。

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

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)...

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

于 2014-10-29T09:01:05.860 回答