python这些奇奇怪怪的概念确实挺多了,不过好在终于快了解完了。闲话不多说,本问讲一下我对装饰器的理解,看过一遍装饰器之后,有点雾里看花的感觉,虽然可以强记下来,不过理解其中精要才是最该做的。
一,为什么需要装饰器?
当一个函数已经上线工作之后,虽然它本身没有问题,但是当需要添加功能的时候就比较麻烦了。首先要清楚,既然该函数已经可以在工作,那么再去修改这个函数的源代码就不合理也不现实了。所以就需要一种非入侵的方式,去在现有函数的基础上添加新功能。装饰器就是干这个事儿的。
那么,就来明确一下装饰器的目标。
- 不改变原有函数的源代码
- 不改变原有函数的调用方式,就是说如果原来在程序中调用test(1,2,3),那么添加新功能之后,依然用test(1,2,3)调用
二,怎么去实现装饰器?
首先我们不考虑python提供的装饰器,如何比改变原有函数的源代码,去添加功能?其实很简单,把函数作为参数传递给另一个高阶函数封装一下就可以了。
def work(i):
print('工作函数执行... 参数(%d)' % i )
def log(func, i):
print('%s 开始执行' % func.__name__)
func(i)
print('%s 结束执行' % func.__name__)
log(work, 8)
Output:
work 开始执行
工作函数执行... 参数(8)
work 结束执行
但是问题也很明显,不满足第二个要求,之前调用work(i) 现在得调用 log(work, i)。那么解决办法就是,把原来函数变成经过封装的函数。
def work(i):
print('工作函数执行... 参数(%d)' % i )
def log(func):
def wapper(*args, **kw):
print('%s 开始执行' % func.__name__)
func(*args, **kw)
print('%s 结束执行' % func.__name__)
return wapper
work = log(work) #work指向了经过我们封装的函数
work(8)
Output:
work 开始执行
工作函数执行... 参数(8)
work 结束执行
到这里,原来怎么调用work 现在依然怎么调用work,不过需要在前面加上一句 work = log(work)。
题外话:如果看不懂log函数是如何实现的,首先要理解python中的函数也是对象,这个指代函数的符号我们完全可以当作变量来对待,去当作参数传递,去作为返回值,去修改它指向新的函数。然后可以看一看 python函数参数的传递,这里这样写是可以接收任何参数的。
可以参考https://blog.csdn.net/qq_21294095/article/details/85074646
三,python提供的装饰器
python提供了@来简化装饰器书写,还是先看代码
def log(func):
def wapper(*args, **kw):
print('%s 开始执行' % func.__name__)
func(*args, **kw)
print('%s 结束执行' % func.__name__)
return wapper
@log
def work(i):
print('工作函数执行... 参数(%d)' % i )
work(8)
print(work.__name__)
Output:
work 开始执行
工作函数执行... 参数(8)
work 结束执行
wapper
只需要在需要装饰的函数前@log 就完全等价于 我们的 work = log(work)。
这里我故意在最后输出了work.__name__ 可以看到 经过装饰后work函数的__name__属性已经改变了,变成了wapper。我们需要让它保持不变。python依然提供了一个函数去做这个事儿。当然你如果像手动做估计也没人拦得住
import functools
def log(func):
@functools.wraps(func) #添加这个装饰器 那么func函数的属性就不会被修改
def wapper(*args, **kw):
print('%s 开始执行' % func.__name__)
func(*args, **kw)
print('%s 结束执行' % func.__name__)
return wapper
@log
def work(i):
print('工作函数执行... 参数(%d)' % i )
work(8)
print(work.__name__)
Output:
work 开始执行
工作函数执行... 参数(8)
work 结束执行
work
四,如果新功能需要参数该怎么办?
如果完全理解了前面的内容,那么使用闭包可以实现传参,把不含参数的装饰器作为内层函数,那么它就可以引用外层的参数。
关于python闭包的概念可以参考https://blog.csdn.net/qq_21294095/article/details/85093573
from functools import wraps
def log(msg):
def deco(func):
@wraps(func)
def wapper(*args, **kw):
print('新功能的参数 %s'% msg)
print('%s 开始执行' % func.__name__)
func(*args, **kw)
print('%s 结束执行' % func.__name__)
return wapper
return deco
@log('添加的新功能所需的参数')
def work(i):
print('工作函数执行... 参数(%d)' % i )
work(8)
Output:
新功能的参数 添加的新功能所需的参数
work 开始执行
工作函数执行... 参数(8)
work 结束执行