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笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。