最近刚开始学习Python,发现这个语言在思想上和Java有许多共同点,但同时也存在着许多不同,此次特意记录一下Python中的装饰器的用法及优点。
文章参考自慕课网的Python教程:https://www.imooc.com/video/10832
1. 闭包
现在我们通过慕课教程中的例子来体会一下我们为什么需要闭包:
假设需求是这样的:需要一个程序判断一个学生的分数是否及格?但是满分可能是100或150,针对不同的满分我们要写两个不同的函数:
def func_100(score):
passline = 60 #满分100的及格线
if score>=passline:
print('pass')
else:
print('fail')
def func_150(score):
passline = 90 #满分150的及格线
if score>=passline:
print('pass')
else:
print('fail')
score = 89
func_100(score)
func_150(score)
得到的输出为:
pass
fail
上面我们编写了两个函数完成了任务,但是可以发现两个函数之间代码的重复度十分高,只有及格线passline不同。这样是很难看的,并且需要编写两个几乎一样的函数,最关键的是想要更改该函数时必须在两个函数中修改两次,非常不方便维护。
所以,我们接下来优化一下我们的代码:
def set_passline(passline):
def check(score):
if score >= passline:
print('pass')
else:
print('fail')
return check #返回检查是否及格的函数
score = 89
#设置及格线为60
check_func = set_passline(60) #得到的是check()这个函数,这个函数中已经加入了passline=60的属性
check_func(score)
#设置及格线为90
check_func = set_passline(90)
check_func(score)
由上面的代码可以看出,我们只是用了一个函数set_passline就解决了问题(包含内嵌函数)。但是我们不免有个疑问,check函数中使用的passline为什么可以引用到外层函数传入的参数?在外层函数执行结束之后该变量已经没有引用,理应被解释器废弃,但是为什么在内嵌函数check中又能访问到呢?
这就是我们要说的闭包:内部函数中对encloing(函数内部与内嵌函数之间)作用域的变量进行操作。因为有了闭包,内嵌函数可以保存encloing作用域的变量,我们简单验证一下:
def set_passline(passline):
def check(score):
print('passline.id= %x' % id(passline)) #打印出passline的id
if score >= passline:
print('pass')
else:
print('fail')
return check #返回检查是否及格的函数
score = 89
#设置及格线为60
check_func = set_passline(60) #得到的是check()这个函数,这个函数中已经加入了passline=60的属性
check_func(score)
print('check_func:',check_func.__closure__) #打印出函数check_func的信息
可以得到以下输出信息:
passline.id= a6a560
pass
check_func: (<cell at 0x7fc7c4c78768: int object at 0xa6a560>,)
我们可以看到函数check_func中包含着一个int类型的对象,id与passline一样,证明了我们的说法。
2. 装饰器
装饰器是Python中非常重要的技术,也是闭包在Python中的一个应用,下面通过将上面的检查学生成绩是否及格进行修改来说明装饰器的概念。
首先,我们增加一个简单的需求,就是要在每次检查学生成绩前、后打印一条日志: 我们先编写一个检查成绩的函数:
def check(score,passline):
if score >= passline:
print('pass')
else:
print('fail')
print('检查开始。。')
check(89,60)
print('检查结束。。')
如上面代码,我们必须在每次调用函数的时候手动添加要打印的日志,非常麻烦。有的同学会说,那我们加到check函数里面不就好了?这样是没错,我们这里只是简单的打印一条日志,如果实际需求是需要在函数check执行前后加入比较复杂的功能,并且需要用到check函数外部作用域的对象时,就显得比较麻烦,并且函数之间的耦合度增加,不利于功能的拆分和后期维护。
所以,我们就可以用到Python中的装饰器,将上面代码修改一下:
#打印
def logger(func):
def in_logger(score,passline):
print('检查开始。。')
func(score,passline)
print('检查结束。。')
print('call logger..')
return in_logger
@logger
def check(score,passline):
if score >= passline:
print('pass')
else:
print('fail')
check_func = check(89,60)
如上代码的输出为:
call logger..
检查开始。。
pass
检查结束。。
所以,我们知道当解释器检测到:’@logger’时,就会去执行函数logger:将函数check(score,passline)传入logger(func),打印出’call logger..’并返回内层函数in_logger(score,passline),需要注意的是,这里返回的是一个函数,而不是函数执行后的输出。
所以,当我们执行check函数时,其实是执行了函数in_logger:
check_func = check(89,60)
||
||
check_func = in_logger(89,60)