首页 > 编程笔记

Python装饰器精讲

装饰器是一个比较新的功能,使用装饰器可以大幅减少代码量,并且可以做出很多有趣的功能。在各种 Python 框架中定义了多种装饰器,可以说装饰器已经成为 Python 必不可少的语法部分。

要想理解装饰器,首先要知道的一点是,在 Python 中,函数和变量一样,都是可以当作参数来传递的。或者更加简单地说,函数也是一个对象,在很多地方它和传统的变量是没有区别的,它只是一个可以调用的对象。
>>> def hello():                              # 定义一个简单的函数
...     print("hello() is Running")
...                                           # 函数定义结束
>>> def indirect_call_func_obj(func_obj):     # 输入参数其实是一个函数
...     if func_obj is not None and callable(func_obj):
...         func_obj()
...                                           # 函数定义结束
>>> indirect_call_func_obj(hello)             # 注意,参数是一个函数
hello() is Running

装饰器是这样的一个函数,其输入是一个函数,输出也是一个函数。下面就是这样的一个例子:
def hello():
    print("hello() is Running")
def indirect_call_func_obj(func_obj):
    def __decorator():
        print('Enter Decorator')
        func_obj()
        print('Exit Decorator')
    return __decorator
if __name__ == "__main__":
    decorator_func = indirect_call_func_obj(hello)
    decorator_func()
运行该脚本,输出如下:

$ python funcDemo2.py
Enter Decorator
hello() is Running
Exit Decorator


从输出可以看到,在运行 hello() 之前和之后都做了一点其他的事情。这些事情就是在函数 __decorator() 内定义的。这就是装饰器的原型了,它将一个完整的事情分成两部分,一部分是我们常见的,即 hello() 部分,另外一部分完成一些辅助功能,即 __decorator() 部分。

还需要注意的是,我们直接调用的不是 hello() 这部分代码,而是 __decorator() 这部分的代码,__decorator() 代码包含 hello() 的信息。

可以使用 @ 来将这两部分功能连接起来,将 @ 放在 hello() 函数的上一行,而且带有 indirect_call_func_obj 这个参数。这时就不需要先调用 indirect_call_func_obj() 来得到一个函数,然后再调用这个返回的函数了,而可以直接调用 hello() 这个函数。代码变成了下面的样子:
def indirect_call_func_obj(func_obj):
    def __decorator():
        print('Enter Decorator')
        func_obj()
        print('Exit Decorator')
    return __decorator
@indirect_call_func_obj
def hello():
    print("hello() is Running")
if __name__ == "__main__":
    hello()
运行结果如下:

$ python funcDemo3.py
Enter Decorator
hello() is Running
Exit Decorator

从输出内容可以看出,修改后的代码和原来的代码运行效果是一样的,只是语法表述的方式不同。其实到这里最基本的装饰器就介绍完了。

被装饰函数带有参数

接下来介绍如何给带有参数的函数装上装饰器。

由于实际调用的是装饰器,所以这个参数肯定是先传给装饰器函数的,但是我们可以将这个参数再转给被装饰的函数。例如下面的例子:
# 装饰器函数,也就是输入一个函数,返回一个函数的函数
def indirect_call_func_obj(func_obj):
    def __decorator(argument):
        print('Enter Decorator')
        func_obj(argument)
        print('Exit Decorator')
    return __decorator
@indirect_call_func_obj
def hello(argument):    # 被装饰函数
    print("hello(%s) is Running" % argument)
if __name__ == "__main__":
    hello("example3")
该函数的输出结果如下:

$ python funcDemo4.py
Enter Decorator
hello(example3) is Running
Exit Decorator


如果参数个数不确定,可以使用*来解决该问题,例如下面的例子:
# 装饰器函数,也就是输入一个函数,返回一个函数的函数
def indirect_call_func_obj(func_obj):
    def __decorator(*argument):                         # 被装饰器要用的参数
        print('Enter Decorator')
        func_obj(*argument)                             # 将参数传给被装饰函数
        print('Exit Decorator')
    return __decorator
@indirect_call_func_obj
def hello_0arg():                                       # 被装饰函数
    print("hello_0arg() is Running")
@indirect_call_func_obj
def hello_1arg(argument):                               # 被装饰函数
    print("hello_1arg() is Running")
@indirect_call_func_obj
def hello_2arg(argument1, argument2):                   # 被装饰函数
    print("hello_2arg() is Running")
@indirect_call_func_obj
def hello_3arg(argument1, argument2, argument3):        # 被装饰函数
    print("hello_3arg() is Running")
if __name__ == "__main__":
    hello_0arg()                                                                                # 没有参数
    hello_1arg("example4")                              # 1个参数
    hello_2arg("example4", "2")                         # 2个参数
    hello_3arg("example4", "2", "arguments")            # 3个参数
运行结果如下:

$ python funcDemo5.py
Enter Decorator
hello_0arg() is Running
Exit Decorator
Enter Decorator
hello_1arg() is Running
Exit Decorator
Enter Decorator
hello_2arg() is Running
Exit Decorator
Enter Decorator
hello_3arg() is Running
Exit Decorator


其实使用 ** 也是可以的,例如下面的例子:
# 装饰器函数,也就是输入一个函数,返回一个函数的函数
def indirect_call_func_obj(func_obj):
    def __decorator(*args, **kwargs):
        print('Enter Decorator')
        func_obj(*args, **kwargs)
        print('Exit Decorator')
    return __decorator
@indirect_call_func_obj
def hello_0arg():                                      # 被装饰函数
    print("hello_0arg() is Running")
@indirect_call_func_obj
def hello_1arg(argument):                              # 被装饰函数
    print("hello_1arg() is Running")
@indirect_call_func_obj
def hello_2arg(argument1, argument2):                  # 被装饰函数
    print("hello_2arg() is Running")
@indirect_call_func_obj
def hello_3arg(argument1, argument2, argument3):       # 被装饰函数
    print("hello_3arg() is Running")
if __name__ == "__main__":
    hello_0arg()
    hello_1arg("example4")
    hello_1arg(argument="example4")
    hello_2arg("example4", "2")
    hello_2arg(argument1="example4", argument2="2")
    hello_3arg("example4", "2", "arguments")
    hello_3arg(argument1="example4",                    # 3个关键字参数
               argument2="2",
               argument3="arguments")
运行结果如下:

$ python funcDemo6.py
Enter Decorator
hello_0arg() is Running
Exit Decorator
Enter Decorator
hello_1arg() is Running
Exit Decorator
Enter Decorator
hello_1arg() is Running
Exit Decorator
Enter Decorator
hello_2arg() is Running
Exit Decorator
Enter Decorator
hello_2arg() is Running
Exit Decorator
Enter Decorator
hello_3arg() is Running
Exit Decorator
Enter Decorator
hello_3arg() is Running
Exit Decorator

装饰函数带有参数

我们知道,函数可以带上参数,装饰器也是一个函数,所以它也可以带上参数。但是装饰器有一个固定的参数,就是输入的函数,即被装饰函数。那么如何给装饰器加上参数呢?

我们可以添加一个外层装饰器,该装饰器带有另外一个参数,这样装饰器从外面看起来就有两个参数了。下面就是这样的一个例子。
# 装饰器参数
def deco_2(deco_arg):   # 外层装饰器,其参数就是装饰器的参数
    def _deco2(arg):            # 内层装饰器,就是一个普通装饰器,参数是被装饰函数
        print('Enter Decorator 2')
        print('deco2 arg:', deco_arg)
        def _deco1():
            print('Enter Decorator 1')
            arg()
            print('Exit Decorator 1')
        return _deco1
        print('Exit Decorator 2')
    return _deco2
@deco_2(deco_arg="deco2_arg")
def hello():
    print("hello() is Running")
if __name__ == "__main__":
    hello()
运行后的输出结果如下:

$ python funcDemo7.py
Enter Decorator 2
deco2 arg: deco2_arg
Enter Decorator 1
hello() is Running


如果被装饰函数也有参数,则可以在内层装饰器携带被装饰函数的参数。下面就是这样一个例子。
# 装饰器参数,也就是输入一个函数,返回一个函数的函数
def deco_2(deco_arg):
    def _deco2(arg):
        print('Enter Decorator 2')
        print('deco2 arg:', deco_arg)
        def _deco1(user):                    # 被装饰函数的参数放在这里
            print('Enter Decorator 1')
            arg(user)
            print('Exit Decorator 1')
        print('Exit Decorator 2')
        return _deco1
    return _deco2
@deco_2(deco_arg="deco2_arg")
def hello(user):
    print("hello(%s) is Running" % user)
if __name__ == "__main__":
    hello('alex')
运行后的输出结果如下:

Enter Decorator 2
deco2 arg: deco2_arg
Exit Decorator 2
Enter Decorator 1
hello(alex) is Running
Exit Decorator 1

装饰函数带有返回值

我们知道,装饰器也是一个函数,所以其执行时也会有返回值。例如下面的例子中,装饰器就返回一个特定的值。
# 装饰器函数,也就是输入一个函数,返回一个函数的函数
def deco1(func_obj):
    def __decorator():
        print('Enter Decorator')
        func_obj()
        print('Exit Decorator')
        return "docorator-ret"
    return __decorator
@deco1
def hello():                        # 被装饰函数
    print("hello() is Running")
    return "hello-ret"
if __name__ == "__main__":
    ret = hello()
    print("ret:", ret)
运行后的输出结果如下:

$ python funcDemo9.py
Enter Decorator
hello() is Running
Exit Decorator
ret: docorator-ret


如果希望返回被装饰函数的返回值,可以在装饰器中返回被装饰函数的返回值。下面就是这样的一个例子。
# 装饰器函数,也就是输入一个函数,返回一个函数的函数
def deco1(func_obj):
    def __decorator():
        print('Enter Decorator')
        hello_ret = func_obj()
        print('Exit Decorator')
        return hello_ret
    return __decorator
@deco1
def hello():                                    # 被装饰函数
    print("hello() is Running")
    return "hello-ret"
if __name__ == "__main__":
    ret = hello()
    print("ret:", ret)
运行后的输出结果如下:

$ python funcDemo10.py
Enter Decorator
hello() is Running
Exit Decorator
ret: hello-ret

使用多个装饰器

如果某个函数有多个装饰器同时装饰,会发生什么情况呢?真实情况是它先使用第一个装饰器包装第二个装饰器,再使用第二个装饰器包装后面的装饰器,最后一个装饰器再包装被装饰的函数,即装饰器之间形成的是嵌套的调用关系,而不是平行关系。
def deco1(func_obj):                    # 装饰器1
    def __decorator():
        print('Enter Decorator 1')
        func_obj()
        print('Exit Decorator 1')
    return __decorator
def deco2(func_obj):                    # 装饰器2
    def __decorator():
        print('Enter Decorator 2')
        func_obj()
        print('Exit Decorator 2')
    return __decorator
@deco1                                  # 第一个使用的装饰器,在嵌套的最外层
@deco2                                  # 第二个使用的装饰器
def hello12():                          # 被装饰函数
    print("hello() is Running")
    return "hello-ret"
@deco2                                  # 第一个使用的装饰器,在嵌套的最外层
@deco1                                  # 第二个使用的装饰器
def hello21():                          # 被装饰函数
    print("hello() is Running")
    return "hello-ret"
if __name__ == "__main__":
    hello12()
    print("*** === *** === *** === ***")
    hello21()
运行后的输出结果如下:

$ python funcDemo11.py
Enter Decorator 1
Enter Decorator 2
hello() is Running
Exit Decorator 2
Exit Decorator 1
*** === *** === *** === ***
Enter Decorator 2
Enter Decorator 1
hello() is Running
Exit Decorator 1
Exit Decorator 2

通过输出结果,可以很容易看出这个嵌套的调用关系及嵌套的顺序。

优秀文章