学习目标:
装饰器、切面编程,实际使用体会。
纸上学来终觉浅
装饰器和切面编程:
常见的函数,我们一般的返回值一个常见的数值或者列表,比如:
def mysum(a,b):
return a + b
result = mysum(1,2)
print(result)
mysum函数返回的是整数1+2的值,为3.
但是python神奇的地方就是,函数的返回值,可以是另一个函数。比如:
def mysum_log():
print('input two number for sum ')
def mysum(a,b):
return a + b
return mysum
mysum2= mysum_log()
print(mysum2(1,2))
这样,mysum_log的函数值是一个函数,赋给了变量mysum2,然后print直接用mysum2(1,2)就可以运行函数.此时的mysum2函数有点类似于mysum的功能,但是增加了print功能。
运行结果:
input two number for sum
3
我们其实可以把变量mysum2直接改成mysum,改一下名字而已,结果就有点神奇了:
def mysum_log():
print('input two number for sum ')
def mysum(a,b):
return a + b
return mysum
mysum= mysum_log()
print(mysum(1,2))
这样我们看起来像调用了mysum函数,但实际上我们还多了打印日志的print功能。业界成这个叫闭包–鬼知道为啥要叫这么个名字,还以为包pi有关。
这有什么用呢?单纯看没啥用,但是当这个mysum方式最初是别人写的,而且别人不愿意改他的函数,可以通过这种方式丰富函数的功能。
举个例子,隔壁老王写了个函数:
def laowangsum(a, b):
return a ^ 2 + b ^ 2
laowangsum的功能是求平方和,但是没有解释,会让人产生误解,你叫老王修改函数,增加注释,他懒不愿意动,怎么办?
此时你想到能不能偷梁换柱,帮他增加点注释,让你的函数调用laowangsum会添加注释log。于是:
def laowangsum(a, b):
return a ^ 2 + b ^ 2
def mysum_log():
print('老王写的sum函数,实际是两个数平方和 ')
return laowangsum
laowangsum = mysum_log()
print(laowangsum(1, 2))
你调用的laowangsum函数,实际上多运行了你的注释,偷偷改了老王的函数的功能。这个是闭包的作用。
另一个需求:我们要求对每个函数都打印出日志信息,以便于错误时定位是哪个函数出了问题。或者进行函数大屏监控,怎么办?
为每个函数都加入一个写log的代码?重复性太高,我们想办法抽象起来,既然闭包可以改变函数功能,那用闭包就可以实现,但是现在的闭包需要写明具体函数,不可能以后新增一个函数都去改一下闭包函数。此时就需要装饰器,也叫切面编程。简单来讲,我把一个代表函数的变量传进闭包函数,然后这个闭包函数就可以自适应传进去的函数,自动调用它,不用每次新增函数都去修改闭包,只用调用一次闭包函数即可。
import functools
def log_decorator(function):
@functools.wraps(function)
def wrapper(*args,**kwargs):
print('log begin')
function(*args,**kwargs)
print('log end')
return wrapper
比如老王的函数定义时,加上一个@log_decorator,就可以自动帮他写日志,他不用管日志这块:
import functools
def log_decorator(function):
@functools.wraps(function)
def wrapper(*args,**kwargs):
print('log begin')
result = function(*args,**kwargs)
print('log end')
return result
return wrapper
@log_decorator
def laowangsum(a, b):
return a ^ 2 + b ^ 2
print(laowangsum(1, 2))
运行结果:
log begin
log end
7
可以看到老王虽然自己只实现了加法逻辑,但是函数运行时,却自动多了写日志的功能,就像函数被砍了两刀,自动加上了写log的功能一样。所以叫切面编程。
而且用print(laowangsum.name)打印出来,laowangsum确实就是老王自己定义的函数,名字没有变化,没有被偷梁换柱,因为是@functools.wraps(function)的原因。
装饰类:
除了函数,还可以用装饰类,大同小异。把函数替换成类
class log:
def __init__(self,func):
self.func = func
self.num_calls = 0 #函数调用次数
def __call__(self, *args, **kwargs):
self.num_calls += 1
print('number of calls is :{}'.format(self.num_calls))
return self.func(*args, **kwargs)
@log
def example():
print('I am being called')
example()
example()
example()
结果:
number of calls is :1
I am being called
number of calls is :2
I am being called
number of calls is :3
I am being called
log类自动帮助统计了函数被调用次数。
结论:
装饰器经常用在:
1、 写日志
2、统计运行时间
3、 校验输入数据的有效性和用户认证等