本文将从以下几点讨论python3中的装饰器:
-
闭包传递函数引用
-
什么是装饰器
-
@注解完成装饰器
-
装饰器的装饰time
-
装饰带参数的函数
-
装饰带返回值的函数
-
装饰器带参数
-
多个装饰器
-
保留被装饰函数的__name__属性
在python中函数,对象统统都可以赋值给变量,如果把一个函数赋值给一个变量,那么通过这个变量就可以调用函数。有关闭包可查阅小编涂鸦之作—【python3闭包】。这里我们将函数直接传到闭包里完成调用。
def log(func):
def wrap():
func()
return wrap
def echo():
print("hello decorator!")
f = log(echo)
f()
------------------------------------------
hello decorator!
什么是装饰器
通常有些函数功能是非业务性质的(权限、日志、事务等),我们希望将这些函数功能植入到业务函数处理中,又不想破坏业务函数的定义,这种在运行期动态增强功能的方式就是装饰器。上面的示例中,显然已经拿到了目标函数的引用,我们就可以在目标函数执行前、执行后进行增强。这里我们在函数执行前后打印日志。
def log(func):
def wrap():
print("log start...")
func()
print("log end...")
return wrap
def echo():
print("hello decorator!")
f = log(echo)
f()
------------------------------------------
log start...
hello decorator!
log end...
注解完成装饰器
结合示例1和示例2我们就完成了装饰器的使用,没有改变目标函数定义,增强了目标函数功能。但是在调用上略显吃力,我们可以直接使用@+装饰器函数置于目标函数上,完成装饰操作。
def log(func):
def wrap():
print("log start...")
func()
print("log end...")
return wrap
@log
def echo():
print("hello decorator!")
echo()
------------------------------------------
log start...
hello decorator!
log end...
装饰器的装饰time
python3是解释型编程语言,从上至下依次解释执行。所以在目标函数执行前装饰器已经装饰好了。下面我们并没有调用任何函数,执行时装饰器已生效。
def log(func):
print("decorator...")
def wrap():
print("log start...")
func()
print("log end...")
return wrap
def echo():
print("hello decorator!")
------------------------------------------
decorator...
装饰带参数的函数
很多时候目标函数都是带参数的,这里使用标准的不定长传参,注意在调用目标函数时,元组参数args要加*,关键字参数kwargs加**,用于拆包,否则目标函数接收到的是一个元组和字典。函数定义时,*args和**kwargs用来声明将元组参数接收到args中,将关键字参数接收到kwargs中。
import time
def log(func):
print("decorator...")
def wrap(*args, **kwargs):
start = time.time()
print("func start at %f" % start)
func(*args, **kwargs)
end = time.time()
print("func end at %f, elaps: %f" % (end, (end - start)))
return wrap
@log
def echo(name, *args, **kwargs):
time.sleep(1)
print(name, args, kwargs)
if __name__ == '__main__':
echo("码农小麦", 100, 200, age=30)
------------------------------------------
decorator...
func start at 1611731478.221400
码农小麦 (100, 200) {
'age': 30}
func end at 1611731479.221400, elaps: 1.000000
装饰带返回值的函数
目标函数有返回值的情况很容易处理,经过上面的示例,我们知道装饰器就是将目标函数作为变量传入,然后由装饰器触发目标函数。带有返回值的函数,只需由装饰器内部接收返回值,并返回即可。
def log(func):
def wrap(*args, **kwargs):
return func(*args, **kwargs)
return wrap
@log
def echo():
return "200 ok"
if __name__ == '__main__':
print(echo())
------------------------------------------
200 ok
装饰器带参数
很多时候装饰器自身也需要参数支持,比如记录日志我们希望知道是哪个业务模块的,或日志的级别等。上面的示例装饰器用来接收函数变量名了,装饰器内部的嵌套函数用来接收目标函数参数了,所以装饰器想要接收参数只能再来一层嵌套函数了。
def log(name):
def wrap(func):
def logging(*args, **kwargs):
print("%s log start..." % name)
func(*args, **kwargs)
print("%s log end..." % name)
return logging
return wrap
@log("order")
def echo(num):
print("echo %d" % num)
if __name__ == '__main__':
echo(200)
------------------------------------------
order log start...
echo 200
order log end...
多个装饰器
有时需要将多个装饰器作用于目标函数,使用上和单个装饰器并无区别。下面我们使用两个装饰器作为演示,其实装饰器本质就是传递函数引用,且只能作用于函数。第一个装饰器找不到函数就会向下继续执行,第二个装饰器找到目标函数进行装饰然后返回内部函数,第一个装饰器就以第二个装饰器返回的函数作为目标函数,所以装饰器最后的执行顺序就是装饰器声明的顺序,而装饰的顺序是由近到远(远近是相对于目标函数而言)。
import time
def calc_time(func):
print("calc_time decorator...")
def wrap():
start = time.time()
print("time start at %f..." % start)
func()
end = time.time()
print("time end at %f, elaps: %f" % (end, (end - start)))
return wrap
def log(func):
print("log_biz decorator...")
def wrap():
print("log start...")
func()
print("log end...")
return wrap
@calc_time
@log
def echo():
print("hello python3")
if __name__ == '__main__':
echo()
------------------------------------------
log_biz decorator...
calc_time decorator...
time start at 1611736239.676400...
log start...
hello python3
log end...
time end at 1611736239.676400, elaps: 0.000000
保留被装饰函数的__name__属性
目标函数被装饰器装饰后,由于目标函数是作为参数传递到装饰器函数中,而装饰器函数内部是返回嵌套函数的引用,所以查看目标函数的__name__属性时,发现属性名称为装饰器内部嵌套函数名称。我们并不需要对__name__属性进行赋值操作,python内置functools.wraps可以解决这个问题。
import functools
def log(func):
def wrap():
func()
return wrap
def log1(func):
@functools.wraps(func)
def wrap():
func()
return wrap
@log
def echo():
print("hello decorator!")
@log1
def echo1():
print("hello decorator!")
if __name__ == '__main__':
print(echo.__name__)
print(echo1.__name__)
------------------------------------------
wrap
echo1