Python:初识装饰器(decorator)

在讲如何写一个装饰器之前,我们首先应该明白两个问题:

  1. 什么是装饰器?
    Python提供了一个特殊的符号:@,这个符号可以理解为一种语法。
@dec
def fun(*args, **kwargs):
	pass

以上就是装饰器的基本形式,dec就是一个装饰器,而fun就是被dec装饰的函数。
以上代码段实际上可以转换为如下形式:

def fun(*args, **kwargs):
	pass
fun = dec(fun)

在日常开发中,绝大部分会使用第一种使用方式,但第二种方式看起来比较直观,易于理解,它诠释了@这个符号到底做了些什么:将被装饰函数当做参数,传入装饰器,并由装饰器返回一个新的函数。
2. 为什么要使用装饰器?

装饰器是AOP(面向切面编程)的一种实现方式,关于AOP的概念和理解,网上有很多专业的回答,这里我就不赘述了。
举一个例子:

def fun_1():
	pass	

def fun_2():
	pass

def fun_3():
	pass

def main():
	fun_1()
	fun_2()
	fun_3()

if __name__ == '__main__':
	main()

main函数依次调用fun_1,fun_2,fun_3三个函数,某一天我发现main函数的执行十分耗时,我需要对其调用过程进行监控,以得知是在哪一步耗时最长,造成了性能的瓶颈。

def main():
	time_1 = time.time()
	fun_1()
	time_2 = time.time()
	fun_2()
	time_3 = time.time()
	fun_3()
	time_4 = time.time()
	print(time_2 - time_1, time_3 - time_2, time_4 - time3)

以上方式很直观,在main函数中将每个调用所花费的时间打印出来即可,但这样做的弊端也很明显。

第一:这种方式直接改变了main函数的函数体,当main函数中调用逻辑非常复杂时,这么做无疑是有风险的。

第二:重复的地方很多,且扩展性很差,试想一下,如果发现了fun_2的调用是性能瓶颈,需要进一步监控fun_2的调用过程(fun_2也调用了很多其他函数来完成自身功能),我就不得不在fun_2中再做一遍这个操作了

基于以上两点,我们可以总结出我们真正想要的,即我们不希望改变原函数,与此同时,对于需要监控调用时间的函数,我们可以很轻易地操作它。那么我么就需要考虑用AOP的思想来进行优化。

AOP(面向切面编程)重点自然是‘切面’二字。就拿上述例子来说,我们希望对fun_1,fun_2,fun_3都监控其调用所花费的时间,目的和纬度都是相同的

那么我们可以将‘监控函数调用所花费的时间’这个功能,进行切面,专门写一个函数(装饰器)来完成这个功能,然后哪里需要它,我们就将它放到哪里,复用性和扩展性立即有了本质的提高。

import time
def timeit(fun):
	"""
	这个装饰器用于打印函数调用所花费的时间
	"""
	def inner(*args, **kwargs):
		before = time.time()
		res = fun(*args, **kwargs)
		after = time.time()
		print('invoke %s use time: %s' % (fun.__name__, after - before))
		return res
	return inner

@timeit
def fun_1():
	time.sleep(0.2)		# 模拟调用fun_1花费的时间

@timeit
def fun_2():
    time.sleep(2)		# 模拟调用fun_2花费的时间

@timeit
def fun_3():
    time.sleep(0.01)	# 模拟调用fun_3花费的时间

def main():
	fun_1()
	fun_2()
	fun_3()

if __name__ == '__main__':
	main()
invoke fun_1 use time: 0.200775146484375
invoke fun_2 use time: 2.0006284713745117
invoke fun_3 use time: 0.010970354080200195

毫无疑问的是,使用装饰器的方式来完成我们希望的功能,是非常简便和易用的,做到了一处定义,处处可用,也没有改变原函数的函数体。

如果有小伙伴暂时还看不懂timeit这个函数是如何成为一个装饰器,并装饰其他函数来为该函数增加新功能的,不要着急继续往下看,相信你会豁然开朗的。

要理解装饰器,我们需要回到Python设计哲学中的重要一环:一切皆对象!

何为一切皆对象?

说起对象这个概念,很多小伙伴或许会认为对象即是某个类实例化之后的结果,但在Python中,这么理解就不正确了。
实际上在Python中能看到的,能用到的,都可以称之为对象,不仅是类的实例,也包括int,str,dict等基本数据类型,还有function,method,class,module等等,这些都是对象。
函数的参数不一定非得是什么数字、字符串之类的东西,函数的返回值也不一定是数字、字符串之类的东西,它们可以是任何对象。
既然函数也是对象,那么函数就可以作为参数传递给另一个函数,而另一个函数的返回值又可以是一个新的函数

这么说可能有点绕,没关系,继续往下看

仍然拿以上示例来说,加入我现在还不知道装饰器这个东西,我又只想打印fun_1调用所花费的时间,并且我不想去更改main函数,那该怎么做呢?
so easy,不让我改main函数,那我就去改fun_1函数

def fun_1():
	before = time.time()
	time.sleep(0.2)		# 模拟fun_1调用所花费的时间
	after = time.time()
	print('invoke %s use time: %s' % ('fun_1', after - before))

实际上这么做,fun_1已经不是原来的那个fun_1了,它已经是一个新的函数,那我何不干脆这么做呢?

def fun_1():
	time.sleep(0.2)		# 模拟fun_1调用所花费的时间
	
def time_fun_1():
	before = time.time()
	fun_1()
	after = time.time()
	print('invoke %s use time: %s' % ('fun_1', after - before))

fun_1 = time_fun_1	# 将fun_1重新指向time_fun_1这个函数,以后调用fun_1实际是去调用time_fun_1

以上代码应该不难理解,但现在突然又需要打印fun_1和fun_2两个函数的调用花费时间呢,依葫芦画瓢的话,那我们就该这么做:

def fun_1():
	time.sleep(0.2)		# 模拟fun_1调用所花费的时间

def fun_2():
	time.sleep(2)		# 模拟fun_2调用所花费的时间
	
def time_fun_1():
	before = time.time()
	fun_1()
	after = time.time()
	print('invoke %s use time: %s' % ('fun_1', after - before))

def time_fun_2():
	before = time.time()
	fun_2()
	after = time.time()
	print('invoke %s use time: %s' % ('fun_2', after - before))

fun_1 = time_fun_1	# 将fun_1重新指向time_fun_1这个函数,以后调用fun_1实际是去调用time_fun_1
fun_2 = time_fun_2	# 将fun_2重新指向time_fun_2这个函数,以后调用fun_2实际是去调用time_fun_2

很明显,new_fun_1和new_fun_2四句话有两句半都是重复的,我不想当个复读机,我想优化它。
观察函数体我们可以发现,实际上只有一个东西不一样:fun_1 or fun_2
那就很简单了,只有一个东西不一样,那就干脆把这个东西当成参数不就行了吗?
于是乎,代码就变成了这个样子:

def time_fun(fun):		# 参数是一个函数
	def inner():
		before = time.time()
		fun()			# 参数fun参与到了inner函数的函数体中
		after = time.time()
		print('invoke %s use time: %s' % (fun.__name__, after - before))
	return inner			# 返回值是一个函数

#@time_fun				# 和fun_1 = time_fun(fun_1)等价
def fun_1():
	time.sleep(0.2)		# 模拟fun_1调用所花费的时间

#@time_fun				# 和fun_2 = time_fun(fun_2)等价
def fun_2():
	time.sleep(2)		# 模拟fun_2调用所花费的时间



fun_1 = time_fun(fun_1)
fun_2 = time_fun(fun_2)

考虑到函数返回值和函数参数的问题,那么就该再优化一下下:

def time_fun(fun):
	def inner(*args, **kwargs):		# inner函数可以接收任何形式的参数
		before = time.time()
		res = fun(*args, **args)	# inner函数将接收到的全部参数,传递给fun,并拿到fun的返回值
		after = time.time()
		print('invoke %s use time: %s' % (fun.__name__, after - before))
		return res		# inner函数的返回值实际上就是fun的返回值
	return inner

好了,装饰器的基本流程和原理讲到这里就算完了。

不过装饰器还有一些其他的特性和形式,比如用到闭包,比如类装饰器,比如装饰器装饰的是method而不是function,这些东西我在之后的博客中也会讲到。

以上讲述如有谬误,请各位小伙伴留言评论吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值