python装饰器学习笔记

刚开始学python时,装饰器这段内容学的晕乎乎的,只知道用@符号可以给函数添加额外功能。记得有次面试被问到装饰器直接就懵逼了。最近完整学了一遍关于装饰器的基础知识,感觉明白了不少。

从'万物皆对象'开始

python中,函数也是对象,也可以作为对象赋值给变量

>>> def hello(name="world"):
        return 'hello, ' + name

>>> nihao = hello
>>> print nihao
<function hello at 0x0000000002B20128>

删除掉原来的函数,变量的赋值依然没变:

>>> del hello
>>> print nihao
<function hello at 0x0000000002B20128>
>>> nihao()
'hello, world'

既然函数是对象,那么它也可以作为另一个函数的返回值:

>>> def get_talk():
        def hi(name="world"):
            return 'hi, ' + name
        return hi

>>> print get_talk
<function get_talk at 0x0000000003235128>

执行get_talk函数, 返回的是hi函数:

>>> print get_talk()
<function hi at 0x0000000003235748>

继续执行,才执行到了hi函数:

>>> print get_talk()()
hi, world

既然函数是对象,那么它也可以作为另一个函数的参数:

>>> def doSomethingBefore(func):
        print "I do something before then I call the function you gave me"
        print func()
>>> doSomethingBefore(hello)
I do something before then I call the function you gave me
hello, world

简单的装饰器

上面的例子中,函数既可以作为另一个函数的参数,也可以作为另一个函数的返回值,把它们综合起来,就可以写出一个简单的装饰器了:

>>> def my_decorator(a_function_to_decorate):

        def wrapper():
            print "Before the function runs"
            a_function_to_decorate()
            print "After the function runs"
            
        return wrapper

在这里,my_decorator接收一个函数a_function_to_decorate作为参数输入,并将a_function_to_decorate经过装饰处理后,以另一个函数wrapper的形式返回。
定义一个待装饰的函数:

def a_function():
    print "I am a alone function"

a_function作为参数传给装饰器:

>>> func = my_decorator(a_function)
>>> func
<function wrapper at 0x000000000325D668>

这时,变量func就被赋值为由a_function经过装饰后的wrapper函数,执行它,就可以看到装饰器的效果:

>>> func()
Before the function runs
I am a alone function
After the function runs

也可以直接将装饰的结果赋值给原函数:

>>> a_function = my_decorator(a_function)
>>> a_function()
Before the function runs
I am a alone function
After the function runs

这里等效为python的装饰器语法@

>>> @my_decorator
def another_function():
    print "Leave me alone"

    
>>> another_function()
Before the function runs
Leave me alone
After the function runs

带输出信息的装饰器

定义这样一个装饰器和待装饰函数:

>>> def my_decorator(func):
        print "I am a decorator! I am executed only when you decorate a function."
        def wrapper():
            print "I am function returned by the decorator"
            func()
        return wrapper

>>> def lazy_function():
        print "zzzzzzzz"

lazy_function用装饰器装饰:

>>> decorated_function = my_decorator(lazy_function)
I am a decorator! I am executed only when you decorate a function.
>>> decorated_function
<function wrapper at 0x000000000325D908>

这里decorated_function = my_decorator(lazy_function),执行my_decorator函数,先打印信息,再把由lazy_function经过装饰生成的函数wrapper返回给decorated_function变量。
再执行这个被装饰后的函数就和前面一样了:

>>> decorated_function()
I am function returned by the decorator
zzzzzzzz

依然可以用装饰器语法:

>>> @my_decorator
def lazy_function_1():
    print "zzzzzzzz"

I am a decorator! I am executed only when you decorate a function.
>>> lazy_function_1()
I am function returned by the decorator
zzzzzzzz

和前面一样,定义被装饰函数的时候,会打印信息。

带参数的装饰器 Beta 1.0

装饰器是以函数作为输入,以函数作为输出的函数。既然是函数,那当然可以带一些别的参数了:

>>> def my_decorator(func, arg1, arg2):
    
        print "I am a decorator! I get two args:", arg1, arg2

        def wrapper():
            print "I am function returned by the decorator. I get two args:", arg1, arg2
            func()
        return wrapper

>>> def lazy_function():
        print "zzzzzzzz"

这里的装饰器除了接收一个函数作为参数外,还接收另外两个字符变量,除此之外和前一个例子没什么区别。

>>> decorated_func = my_decorator(lazy_function, 'a', 'b')
I am a decorator! I get two args: a b
>>> decorated_func()
I am function returned by the decorator. I get two args: a b
zzzzzzzz

创建和执行被装饰函数时,会将参数a, b打印出来。
运用装饰器语法:

>>> @my_decorator(arg1='a', arg2='b')
def lazy_function_1():
    print "zzzzzzzz"

Traceback (most recent call last):
  File "<pyshell#61>", line 1, in <module>
    @my_decorator(arg1='a', arg2='b')
TypeError: my_decorator() takes exactly 3 arguments (2 given)

出错了。。。。

@my_decorator(arg1='a', arg2='b')
def ...

相当于

decorator = my_decorator(arg1='a', arg2='b')
@decorator
def ...

也相当于

lazy_function_1 = my_decorator(arg1='a', arg2='b')(lazy_function_1)

这里看着就别扭了,有两处错误:

  • my_decorator函数参数个数不对;
  • my_decorator函数执行过后的结果是本应该是被装饰后的函数,这时再接收带装饰函数作为输入,不合逻辑。

所以,以上的带参数装饰器连@符号都用不了,都不好意思说它是装饰器了。
要想使用@符号,还要继续封装。

装饰器的生成函数

装饰器也是函数,那它也可以作为另一个函数的输出了。

>>> def decorator_maker():
        print "I make decorators! I am executed only once: " \
              "when you make me create a decorator."

        def my_decorator(func):
            print "I am a decorator! I am executed only when you decorate a function."

            def wrapped():
                print "I am the wrapper around the decorated function. " \
                      "I am called when you call the decorated function. "
                func()
                print "As the wrapper, I return the RESULT of the decorated function."

            print "As the decorator, I return the wrapped function."
            return wrapped

        print "As a decorator maker, I return a decorator"
        return my_decorator

>>> def lazy_function():
        print "zzzzzzzz"

这里,my_decorator装饰器作为了decorator_maker函数的返回值。

>>> new_decorator = decorator_maker()
I make decorators! I am executed only once: when you make me create a decorator.
As a decorator maker, I return a decorator

执行decorator_maker函数,将装饰器赋值给new_decorator变量。
接下来就可以和之前一样,创建被装饰函数,并执行它了:

>>> decorated_function = new_decorator(lazy_function)
I am a decorator! I am executed only when you decorate a function.
As the decorator, I return the wrapped function.
>>> decorated_function()
I am the wrapper around the decorated function. I am called when you call the decorated function. 
zzzzzzzz
As the wrapper, I return the RESULT of the decorated function.

@语法:

>>> @new_decorator
def lazy_function_1():
    print "zzzzzzzz"

I am a decorator! I am executed only when you decorate a function.
As the decorator, I return the wrapped function.

既然new_decoratordecorator_maker函数的返回值,那么再直接一点呢?

>>> @decorator_maker()
def lazy_function_1():
    print "zzzzzzzz"

I make decorators! I am executed only once: when you make me create a decorator.
As a decorator maker, I return a decorator
I am a decorator! I am executed only when you decorate a function.
As the decorator, I return the wrapped function.

注意:

  • 这里是先执行了装饰器生成函数decorator_maker,生成了装饰器,再由装饰器生成了被装饰函数。
  • 这里的代码形式和上一节带参数的装饰器报错的代码很像,但这里@符号后面代码执行的结果刚好是装饰器函数,所以可以正确运行。那么,带参数的装饰器的正确的姿势,是不是应该也是这样呢?

带参数的装饰器 Beta 2.0

跟着上一节的思路,把参数加入到装饰器生成函数里:

>>> def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):

        print "I make decorators! And I accept arguments:", decorator_arg1, decorator_arg2

        def my_decorator(func):

            print "I am the decorator. Somehow you passed me arguments:", decorator_arg1, decorator_arg2

            def wrapped(function_arg1, function_arg2):
                print ("I am the wrapper around the decorated function.\nI can access all the variables\n"
                      "\t- from the decorator: {0} {1}\n"
                      "\t- from the function call: {2} {3}\n"
                      "Then I can pass them to the decorated function".format(decorator_arg1, decorator_arg2,function_arg1, function_arg2))
                return func(function_arg1, function_arg2)

            return wrapped

        return my_decorator

这里装饰器生成函数接收了两个参数,并且在装饰器生成函数执行时、装饰器函数执行时、被装饰函数执行时,都打印这两个参数,这里可以充分体现出闭包的特性。
再定义一个带装饰函数:

>>> def lazy_function(function_arg1, function_arg2):
        print "zzzzzzz"
        print "I only knows about my arguments: {0} {1}".format(function_arg1, function_arg2)

这个函数也要接收两个参数,但注意区分下装饰器的参数和待装饰函数的参数,并不一样。
先执行一下装饰器生成函数,创建装饰器:

>>> decorator = decorator_maker_with_arguments("Leonard", "Sheldon")
I make decorators! And I accept arguments: Leonard Sheldon

再执行装饰器,创建被装饰函数:

decorated_function = decorator(lazy_function)
I am the decorator. Somehow you passed me arguments: Leonard Sheldon

最后执行被装饰函数:

>>> decorated_function("Rajesh", "Howard")
I am the wrapper around the decorated function.
I can access all the variables
    - from the decorator: Leonard Sheldon
    - from the function call: Rajesh Howard
Then I can pass them to the decorated function
zzzzzzz
I only knows about my arguments: Rajesh Howard

还没完,还是得用@语法来检验一下:

>>> @decorator_maker_with_arguments("Leonard", "Sheldon")
def lazy_function_1(function_arg1, function_arg2):
    print "zzzzzzz"
    print "I only knows about my arguments: {0} {1}".format(function_arg1, function_arg2)

I make decorators! And I accept arguments: Leonard Sheldon
I am the decorator. Somehow you passed me arguments: Leonard Sheldon

装饰器生成函数运行正常,装饰器运行正常!
再运行下被装饰函数:

>>> lazy_function_1("Rajesh", "Howard")
I am the wrapper around the decorated function.
I can access all the variables
    - from the decorator: Leonard Sheldon
    - from the function call: Rajesh Howard
Then I can pass them to the decorated function
zzzzzzz
I only knows about my arguments: Rajesh Howard

例子

  • flask中检查用户是否具有管理员权限
from functools import wraps
def admin_required(func):
    @wraps(func)
    def decorated_func(*args, **kwargs):
        if not current_user.is_authenticated:
            return redirect(url_for('auth.login'))
        if current_user.is_admin:
            return func(*args, **kwargs)
        else:
            abort(403)
    return decorated_func
  • 打印函数执行时间
from functools import wraps
def print_time_cost(func):
    @wraps(func)
    def decorated_func(*args, **kwargs):
        before = datetime.now()
        res = func(*args, **kwargs)
        after = datetime.now()
        print func.__name__ + ': ' + str(after - before)
        return res
    return decorated_func
Comments
Write a Comment
  • Gantalbravexv reply

    非常详细的装饰器讲解