装饰器本原
装饰器就是一个函数,并不需要特殊语法定义。当你在别的函数头上写@时,就相当于调用装饰器函数,传入参数是被装饰函数,返回值也被赋给被装饰函数。参考《Two of the simplest Python decorator examples》
@mydecorator
def myfunc():
pass
等价于
def myfunc():
pass
myfunc = mydecorator(myfunc)
可以见@装饰器,是调用装饰器函数。(1)它可以执行一些一次性的代码,只在定义被装饰函数的时候执行一次。(2)它也可以使得被装饰函数每次调用时都执行一些额外代码。这是通过给原函数加wrapper实现的。
包装
我们在装饰器里定义一个内部函数,它接受的参数与原函数一样,它调用原函数并将这些参数传递给原函数。此外,它还额外执行一些代码。然后我们把这个内部函数作为装饰器的返回值。
普通的wrapper不会保持原函数的.name和.doc等属性。所以最好用标准库提供的@wraps包装我们的wrapper,它会自动将原函数的这些属性赋予我们定义的wrapper。
Without the use of this decorator factory, the name of the example function would have been 'wrapper', and the docstring of the original example() would have been lost. Python文档
例子:
from functools import wraps
def mydecorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
print "Before decorated function"
r = f(*args, **kwargs)
print "After decorated function"
return r
return wrapped
@mydecorator
def myfunc(myarg):
print "my function", myarg
return "return value"
r = myfunc('asdf')
print r
带参数的装饰器(重要pattern!)
默认的装饰器是没有参数的(它的隐含参数是被装饰函数)。如果要定义带参数的装饰器,那就要再套一层。看着有点费解!!反正这种代码我看了很晕。但是这种pattern很常见。
from functools import wraps
def mydecorator_not_actually(count):
def true_decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
for i in range(count):
print "Before decorated function"
r = f(*args, **kwargs)
for i in range(count):
print "After decorated function"
return r
return wrapped
return true_decorator
@mydecorator_not_actually(count=5)
def myfunc(myarg):
print "my function", myarg
return "return value"
r = myfunc('asdf')
print r
装饰器的典型应用
Flask里面的@route就是执行一次性代码的例子。它是一个带参数的装饰器,所以套一层,里面才是真正的装饰器。但是它没有改变原函数,被修饰函数f还是原样返回,只是执行了一些一次性的代码。将这个函数添加到路由表中。
参见:StackOverflow
def route(self, rule, **options):
"""A decorator that is used to register a view function for a
given URL rule. This does the same thing as :meth:`add_url_rule`
but is intended for decorator usage::
@app.route('/')
def index():
return 'Hello World'
For more information refer to :ref:`url-route-registrations`.
:param rule: the URL rule as string
:param endpoint: the endpoint for the registered URL rule. Flask
itself assumes the name of the view function as
endpoint
:param options: the options to be forwarded to the underlying
:class:`~werkzeug.routing.Rule` object. A change
to Werkzeug is handling of method options. methods
is a list of methods this rule should be limited
to (``GET``, ``POST`` etc.). By default a rule
just listens for ``GET`` (and implicitly ``HEAD``).
Starting with Flask 0.6, ``OPTIONS`` is implicitly
added and handled by the standard request handling.
"""
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
Flask文档里面的login_required装饰器,是执行多次代码的例子。它在原函数外面套了一层wrapper,这样每次执行原函数前,都会执行检查是否登陆的代码,如果未登录,直接就返回redirect,而不执行原函数,否则照常执行。
from functools import wraps
from flask import g, request, redirect, url_for
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if g.user is None:
return redirect(url_for('login', next=request.url))
return f(*args, **kwargs)
return decorated_function
另外还有标准库里面的@cache简直是神器,一行代码就实现了memoize,写自顶向下的DP只要普通递归,加这个装饰器就搞定。
网友评论