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() 函数的上一行,而且带有 indirect_call_func_obj 这个参数。这时就不需要先调用 indirect_call_func_obj() 来得到一个函数,然后再调用这个返回的函数了,而可以直接调用 hello() 这个函数。代码变成了下面的样子:还需要注意的是,我们直接调用的不是 hello() 这部分代码,而是 __decorator() 这部分的代码,__decorator() 代码包含 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
声明:《Python系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。