部分内容整理自:python学习路线 课时14、18、19 - 阿里云大学
现实中,经常会碰到借用他人写好的函数的情况。如果我们不满足于已写好的函数功能,但考虑到开闭原则(不能对原函数进行修改,只能对原函数进行拓展),并且为了易于修改和维护,我们引入装饰器函数来实现在不修改原函数的情况下,对原函数进行拓展。
一等对象:(满足以下所有条件)
① 对象是在运行时创建的。
② 能赋值给变量或作为数据结构中的元素。
③ 能作为参数传递。
④ 能作为返回值返回。
注:python中所有能定义的对象都是一等对象,包括函数。
高阶函数:(满足以下至少一个条件)
① 接收一个或多个函数作为参数。
② 将函数作为返回值返回。
我们可以将原函数 old() 传入到一个新函数 new() 中,在新函数中调用旧函数,即可实现旧函数的拓展。
例如当我们需要在 old() 运行前打印“运行开始”,在 old() 运行后打印“运行结束”,可以:
def new():
print('运行开始')
r = old()
print('运行结束')
return r
假如我们有一批需要以同样方式拓展的函数,我们需要得到对应的一系列的新函数。可以定义一个函数,来实现传入一个旧函数,返回一个新函数的目的。我们需要做的,只是把上面那个代码,移到这个生产函数的函数中。
注意:考虑到函数的参数个数、格式不确定,我们需要利用参数打包、解包的知识,对上面的 new() 函数进行一些修改。
def new_generator(old): #传入原函数
def new(*args,**kwargs): #将传入的若干个参数、关键字参数装入args列表和kwargs字典里
print('运行开始')
r = old(*args,**kwargs) #将args列表和kwargs字典里的参数解包
print('运行结束')
return r
return new
像上面我们定义的 new_generator() 函数就是一个装饰器。它能实现对原有函数的拓展(装饰)功能。
先看代码,其中 new_generator() 是装饰器函数,fun() 是我们需要装饰的函数。
def new_generator(old):
def new(*args,**kwargs):
print('运行开始')
result = old(*args,**kwargs)
print('运行结束')
return result
return new
@new_generator
def fun():
print('The world!')
return 0
fun()
运行结果为:
运行开始
The world!
运行结束
可以发现,我们在需要装饰的函数前加了“@new_generator”,即@装饰器函数名。实现了将 fun() 函数作为参数调用 new_generator() 函数,并将结果返回给 fun() 函数这个过程。即上面代码的第二块内容等价于:
def fun():
print('The world!')
return 0
fun = new_generator(fun)
一个函数可不可以使用多个装饰器呢?
使用多个装饰器时的格式和调用装饰器的顺序是怎么样的?
当我多个定义函数前面都加了装饰器语句,会发生冲突吗?
装饰器函数可不可以被装饰?
直接看代码和运行结果就知道了。
。。。。。。(此处我本以为可以自信地完成我的第一篇博客,结果……)
和我预想的结果不一样,看来我们要开个大章了。(如果有人看的话,直接看最后就行了,我应该会在最后总结答案的)
定义函数部分
先解释一下,这里的 newnewnew 和 fun2 我是想测试一下装饰器函数能不能被装饰器装饰。
def new_generator(old): #1
def new(*args,**kwargs):
print('运行开始')
result = old(*args,**kwargs)
print('运行结束')
return result
return new
def newnew_generator(old): #9
def new(*args,**kwargs):
print('试图打断运行')
result = old(*args,**kwargs)
print('打断失败')
return result
return new
@newnew_generator #17这行后面有些测试需要注释掉,会用黑体标出
def newnewnew_generator(old): #18
def new(*args,**kwargs):
print('搞事情开始')
result = old(*args,**kwargs)
print('搞事情结束')
return result
return new
@newnew_generator #26
@new_generator #27
def fun1(): #28
print('The world!')
return 0
@newnewnew_generator #32
def fun2(): #33
print('The world!')
return 0
def fun3(): #37
print('The world!')
return 0
主体部分(这里不同的代码不要写在一起运行,运行其中一个的时候要把其他的注释掉)
(我按照一个代码接一个运行结果的顺序来写。)
1)
if __name__ == '__main__':
fun1()
试图打断运行
打断失败
试图打断运行
运行开始
The world!
运行结束
打断失败
2)
if __name__ == '__main__':
fun2()
试图打断运行
打断失败
搞事情开始
The world!
搞事情结束
3)
if __name__ == '__main__':
fun3()
试图打断运行
打断失败
The world!
我们发现,这三个运行结果的前两行是相同的,这是一个没有运行 fun 函数而只运行了装饰器函数所产生的打印语句。
如果我们把第17行的装饰器语句注释掉,会发现,这两个代码的运行结果就是去掉前两行的结果。即,
试图打断运行
运行开始
The world!
运行结束
打断失败
搞事情开始
The world!
搞事情结束
根据这两个运行结果,我们可以发现,同一个函数是可以用多个装饰器函数装饰的。装饰顺序为从内往外的顺序,处于上方的装饰器语句在运行中处于最外层。例如代码第26~30行,
@newnew_generator
@new_generator
def fun1():
print('The world!')
return 0
这里的 fun1() 函数,先被 new_generator 装饰,然后再被 newnew_generator 装饰。也可以理解为 newnew_generator 装饰了已经被 new_generator 装饰后的 fun1() 函数。等价代码为,
fun1 = newnew_generator(new_generator(fun1))
这样我们就解决了前两个问题。
然后看第三个问题,我 fun1() 和 fun2() 函数都在主体部分运行,python能不能分辨清楚我的装饰器语句?换一句话说,我的装饰器语句针对的是不是后面一个函数,还是后面所有的函数。
同样,为了方便看清运行结果,把第17行注释掉,运行代码。
if __name__ == '__main__':
fun1()
print('————————————')
fun2()
试图打断运行
运行开始
The world!
运行结束
打断失败
————————————
搞事情开始
The world!
搞事情结束
if __name__ == '__main__':
fun2()
print('————————————')
fun1()
搞事情开始
The world!
搞事情结束
————————————
试图打断运行
运行开始
The world!
运行结束
打断失败
这样,第三问的答案也出来了,python很清楚地知道你要装饰什么函数。
最后我们来看,装饰器语句用装饰器装饰后会发生什么后果。
首先,我们一开始运行的三个代码中,可以发现,无论我们运行的是 fun1 还是 fun2 甚至是没用到装饰器的 fun3 都会把我们装饰装饰器的语句(17行)在最前面,空运行一遍。
我们可以再试试,主体部分不放任何语句,直接运行,同样有以下打印结果:
试图打断运行
打断失败
那我们就知道第四问的答案了,直接看下面吧。