python装饰器

python装饰器

部分内容整理自: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行)在最前面,空运行一遍。
我们可以再试试,主体部分不放任何语句,直接运行,同样有以下打印结果:

试图打断运行
打断失败

那我们就知道第四问的答案了,直接看下面吧。

答案
  1. 可以。
  2. 在定义函数的前面若干行(中间可以有空行,但不会跨过一个函数装饰),写上装饰器语句即可。顺序按由内向外(由下至上)的顺序装饰。以打印语句的装饰器为例,写在最上面的装饰器语句,在最外面打印(最先或最后)打印。(需要更具体的见上文)
  3. python清楚地知道,你的装饰器语句只是用来装饰语句后面第一个函数的。
  4. 装饰器函数无法被装饰器装饰。当你在装饰器函数的定义语句前加装饰器语句的话,会在运行你这个模块代码的时候,空运行一遍你这个多余的装饰器语句中涉及的装饰器里的代码。相当于 @new 会运行一遍 new(fun)() ,其中 fun 由 def fun(): pass 定义。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值