在Python中,装饰器在哪些情况下可以大大简化任务?

5

试图找到装饰器在何时非常有益,何时不太有用的示例代码。

欢迎提供示例代码。


代码合约可以让生活更轻松 - 可以通过装饰器实现。 - Hamish Grubijan
5个回答

8

装饰器是一种特定的高阶函数调用的简单语法,因此,如果你只关注语法,它不太可能产生很大的差异。换句话说,无论何时你可以这样说:

@mydecorator
def f(...):
  # body of f

你可以同样地说

def f(...):
  # body of f
f = mydecorator(f)

装饰器语法的优点在于它更加简洁(不需要重复三次f),并且它出现在def(或者对于类装饰器来说,是class)语句之前,从而立即提醒代码的读者。这很重要,但只是不能太棒了!
装饰器的语义(以及与此模式匹配的高阶函数调用的语义,如果没有装饰器的话)是相同的。例如:
@classmethod
def f(cls, ...):

让您创建类方法(特别适用于替代构造函数),并

@property
def foo(self, ...):

让您可以创建只读属性(2.6中的其他相关装饰器用于非只读属性;-),即使不使用它们,它们也非常有用(因为它们可以避免您为本质上是属性的内容编写很多愚蠢的“样板”访问器...仅仅因为对属性的访问可能需要在将来触发一些计算!-)。

除了Python内置的装饰器外,您自己的装饰器可能同样重要--当然这取决于您的应用程序是什么。通常,它们使得将代码的某个部分重构到装饰器中变得容易(否则该部分代码必须在许多函数和类中进行复制[[或者你可能不得不使用元类来处理类的情况,但那些更丰富且更复杂,使用不正确会导致问题]])。因此,它们帮助您避免重复的、样板式的代码--由于DRY,“不要重复自己”,是软件开发的核心原则,任何帮助您实现该原则的工具都应该受到热烈的欢迎。


4
通常的例子是使用@property装饰器来创建只读属性:
@property
def count(self):
    return self._events

替代:

def _get_count(self):
    return self._events

count = property(_get_count)

4

理解装饰器的最简单方法是看一些例子。例如:

假设你正在研究一些代码并希望了解函数何时以及如何被调用。 你可以使用装饰器来改变函数,使其在每次调用函数时打印一些调试信息:

import functools
def trace(f):
    '''This decorator shows how the function was called'''
    @functools.wraps(f)
    def wrapper(*arg,**kw):            
        arg_str=','.join(['%r'%a for a in arg]+['%s=%s'%(key,kw[key]) for key in kw])
        print "%s(%s)" % (f.__name__, arg_str)
        return f(*arg, **kw)
    return wrapper

@trace
def foo(*args):
    pass


for n in range(3):
    foo(n)

打印:

# foo(0)
# foo(1)
# foo(2)

如果您只希望跟踪一个名为foo的函数,您当然可以将代码更简单地添加到foo的定义中:

def foo(*args):
    print('foo({0})'.format(args))

但如果您有许多希望跟踪的函数,或者不想干扰原始代码,则装饰器变得非常有用。

有关其他有用装饰器的示例,请参见装饰器库


你的文档字符串应该应用于 trace,在那里你可以查找它,而不是应用于 wrapper,因为它会被 wraps 销毁。 - Mike Graham

1
在AppEngine API中,有一个很好的@login_required装饰器,可以大大简化代码:
class MyPage(webapp.RequestHandler):
    @login_required
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write("Hello, world!")

相反:

class MyPage(webapp.RequestHandler):
    def get(self):
        user = users.get_current_user()
        if not user:
            return self.redirect(users.create_login_url(self.request.uri))

        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write("Hello, world!")

我经常使用的其他装饰器包括类方法的 @classmethod、静态方法的 @staticmethod,以及(如Mike DeSimone所说)只读属性的 @property。将装饰器放在函数前面比后面更容易阅读,就像这样:
class Bar(object):
    @classmethod
    def foo(cls):
        return id(cls)

改为:

class Bar(object):
    def foo(cls):
        return id(cls)
    foo = classmethod(foo)

它只是节省样板代码。


1

装饰器用于设计选择,其中您正在合并两个概念,例如“日志记录”和“库存管理”或“已注册用户”和“查看最新消息”。

  • 其中一个概念包裹另一个概念,控制其如何被调用。这个概念就是装饰器。
  • 第二个概念永久地与第一个概念结合在一起,以至于失去直接调用第二个概念的能力也是可以接受的。例如,失去了不调用“已注册用户”而只调用“查看最新消息”的能力。

当设计选择正确时,装饰器语法(或用于装饰器的语法糖)读起来清晰,并消除了由于误解而产生的错误。

通常的装饰器概念包括:

  • 日志记录。这可能与事务处理器结合使用,以记录每个成功或失败的交易。
  • 要求安全性。这可能与更改库存中的价格相结合。
  • 缓存(或记忆化)。这可能与净现值计算或任何昂贵的静态只读操作相结合。
  • 语言修复,如“@classmethod”或“将错误返回值转换为异常”
  • 注册框架,例如“当按下该按钮时调用此函数。”
  • 状态机处理,其中装饰器决定下一个要处理的状态。
  • 等等。

您可以查看http://wiki.python.org/moin/PythonDecoratorLibrary(已过时的维基页面)或dectools(http://pypi.python.org/pypi/dectools)库以获取更多文档和示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接