Python装饰器笔记

装饰器本原

装饰器就是一个函数,并不需要特殊语法定义。当你在别的函数头上写@时,就相当于调用装饰器函数,传入参数是被装饰函数,返回值也被赋给被装饰函数。参考《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只要普通递归,加这个装饰器就搞定。

参考

Real Python

你可能感兴趣的:(Python装饰器笔记)