入门向python装饰器理解

装饰器

此为个人学习笔记,内容仅供参考,转载请注明作者,如有错误,欢迎指正。)

记得以前学到这里,看了很多关于python装饰器的文章,对于装饰器还是一知半解,后来在一些实例中, 通过最朴实的方法总算是摸清楚了什么是装饰器?以下是个人对装饰器的笔记。

什么是装饰器?

在学习装饰器之前,我们必须搞清楚一点,装饰器究竟是什么? 用文字说明, 装饰器实际上就是为了给某个函数,或者称某个程序添加一个功能。并且考虑 到该函数或程序已经被引用,所以我们不能对函数或程序本身进行修改,所以 有了装饰器,可以想象成,我们人拿上斧头,拥有了砍树的能力,这就是装饰器的作用。

装饰器需要满足以下两点:

  1. 不修改原程序或函数的代码
  2. 不改变函数或程序的调用方法

ps:此处如果不理解,我们还用人砍树的例子。比如说小明他是个人,你要让他砍树,他怎么砍, 必须要让他拥有砍树能力,那么问题来了,怎么让他拥有这个能力呢?好吧可以给他注射药剂, 让他变强,但是这就不满足上述第一点了,被改造后的小明我们就不认识他了,不知道怎么让他去砍树, 所以就必须有前提条件1,不得修改源代码。 第二条,不改变调用方法。原本把我想让小明去砍树就对 小明下命令,“嘿小明去砍个树”,但是小明不会啊,怎么办呢,让一个会的人带着小明去,这个过程就成了什么呢? 你让某个人带着小明去,而不是你让小明去,这就出现了调用方法的改变。

实例中理解

第一步:装饰器的前奏

定义一个测试的函数,返回输入的值

def test1(a):
	return a

上述定义的函数,它的作用就是返回输入值,那么接下来要考虑给它加一个计算它自身运行时间的功能。最简单的思路是,记录开始时间->运行函数->记录结束时间, 用结束时间减去开始时间就是我们想要的结果了,如下:

import time
start_time = time.time()	# 开始时间
test1(250)					# 要测试的函数
stop_time = time.time()		# 结束时间
delta_time = stop_time - start_time    # 时间间隔,即运行时间

好了,简单地实现了。根据需求应该把这个过程封装在函数内,调用一次就返回test1的运行时间。塞到函数里面大概是这样:

def test2()
	start_time = time.time()	# 开始时间
	test1(250)					# 要测试的函数
	stop_time = time.time()		# 结束时间
	delta_time = stop_time - start_time    # 时间间隔,即运行时间
	print('{:f}'.format(delta_time))  	   # 格式化打印

运行一下

>>> test2(250)
'0.000001'

此处可以看到,将上述过程封装到 test2 之后,通过调用 test2 就能得到 test1 函数的运行时间, 但是不难发现有两个点:
(1)改变了调用方法,根据装饰器的描述,应该直接调用test1就得到运行时间
(2)此处只研究了test1(250)也就是说此时test1只有一个固定参数,这不能保证是否会因为参数的不同运行时间不同。

针对这两个点进一步改进

首先来解决一下关于调用的时候不再是固定参数,如下:

def test2(func):
	def test3(x):
		start_time = time.time()	# 开始时间
		func(x)						# 要测试的函数	
		stop_time = time.time()		# 结束时间
		delta_time = stop_time - start_time    # 时间间隔,即运行时间
		return '{:f}'.format(delta_time)  	   # f返回格式化时间
	return test3

def test1(a):
	return a

运行一下

>>> test2(test3)(250)
'0.000002'

现在有三个函数test1, test2, test3。整个流程还是得梳理清楚,是为了计算test1的运行时间, 那么还是"记录开始时间->运行函数->记录结束时间"这一个流程。

在上面这段代码中,首先把 test1 的函数地址当成参数传入到了 test2 , 此时 test2(func) 相当于 test2(test1),在 test2 中,再定义一个test3。把上述求运行时间的流程放进去,在test3中可以看到一个func(x)func是我们传入的地址也就是test1,要计算test1的运行时间,那肯定是需要调用的, func() 就是 func 的调用,func(x) 相当于 test1(x)

再来看 test2 函数的返回值,是test3的地址,一方面不会因为调用而报错,只是拿了 test3 函数的地址; 一方面是为了取得 test3 以保证后面的展开。现在目的就很明确了。调用test2, 传入test1的地址,又因为是返回了test3的地址,实际上经过一顿操作,test2(test1) 就像是 test3 的地址, test2(test1)() 就是在实例化 test3 了, 即 test3(), 显而易见,其中 x 即最初传入的 250 。现在可以这样来获得各种参数中test1的运行时间。

第二步,实现装饰器

刚刚说到两个点,已经解决了一个,再去解决调用方法。在test1函数加一个@test2,这是一个语法糖, 效果如下:

def test2(func):
	def test3(x):
		start_time = time.time()	# 开始时间
		func(x)						# 要测试的函数	
		stop_time = time.time()		# 结束时间
		delta_time = stop_time - start_time    # 时间间隔,即运行时间
		return '{:f}'.format(delta_time)  	   # f返回格式化时间
	return test3

@test2
def test1(a):
	return a

运行一下

>>> test1(250)
'0.000001'

加入语法糖之后直接调用test1就得出了想要的结果。那么这个语法糖的作用就显而易见了。 就是替代test2(test1)的。

就是说test2(test1)(250) 直接被语法糖省略成了test1(250),这二者是相同的效果,但是满足了不改变调用方法,而且我们自始至终都没有修改过test1里面的源代码。

这就是装饰器。(2018.11.16)

你可能感兴趣的:(python)