python 装饰器 学习总结

装饰器:装饰器本质就是函数,功能是为其它函数添加附加功能

有点类似函数的目的(函数的目的就是当你有1W个代码块要添加相同的功能时,只需要调用已经写过一次的函数就好,而不是要写1W次同样的代码), 当你有1W个函数要添加相同的功能时,只需要调用已经写过一次的修饰器就好;还有就是当你的程序很庞大时,想要给你的程序中的某个模块加上新的功能,不可能说直接修改源代码, 因为说不定修改的部分后面会引用到,擅自修改就可能会报错

装饰器的原则:

1.不修改被修饰函数的源代码
2.不修改被修饰函数的调用方式



装饰器 = 高阶函数+函数嵌套+闭包
高阶函数:
1.函数接收的 参数是一个函数名
2.函数的 返回值是一个函数名
3.只要满足上述条件的其中一个都可以称之为高阶函数
例如:
def a():
	print('from a')
def b(func):		#这个b函数满足第一个条件
	print(func)
def c():		#这个c函数满足第二个条件
	return func		
1.假定这里我们先用高阶函数给原函数写一个统计时间的方法,有:
def a():		#要被统计的函数
	x = sum([i for i in range(1000000)])
	print(x)

def time_test(func):
	import time
	start_time = time.time()
	func()
	end_time = time.time()
	result = end_time - start_time
	return result

res = time_test(a)
print(res)
在此,虽然我们写好了统计时间的函数,但是因为这样做 修改了原来函数a()的调用方法,所以这个肯定算不上是一个装饰器
我们用高阶函数的第一个条件来完成了在不修改原函数代码的情况下对函数增加了额外的功能,但是这里改变了调用方法


2.接着让我们来看高阶函数的第二个用法,用这个可以对函数进行一些巧妙的处理
def a():
	print('from a')

def b(func):
	print('from b')
	# 一堆代码
	return func

a = b(a)
a()	#调用a
作为结果而言,a = b(a)这个式子可以认为是a = a,只不过这里的a是作为b函数的返回值存进去的(既然是返回值,那么说明b函数已经运行了一次)
在此,a这个变量原来存的是a的地址,我们把函数b的返回值赋给a以后(在这里函数b都运行了一次),a的值就不再是函数a的地址了,而是b(a)了, 也就意味着每当执行a函数的时候,都会先把b函数执行一遍
这样做的目的在于, 我们在不改变原有调用方式的情况下(函数a的调用方法还是a()),运行了一个额外的函数。如果我们在这个额外的函数中,写上我们要附加的功能(就是上面的第一点),这样是不是就满足了我们装饰器的要求:在不修改原函数和其调用方式的基础上给其增加新的功能


3.接下来,我们就把条件一(不修改源代码的条件下附加新的功能)和条件二(不修改调用方式的情况下运行一个额外的函数)结合起来:
def a():	#要被统计的函数
	x = sum([i for i in range(1000000)])
	print(x)


def time_test(func):
	import time
	start_time = time.time()
	func()
	end_time = time.time()
	result = end_time - start_time
	print('运行的时间为',result)
        return func

a = time_test(a)
a()	
函数运行的结果为:

我们会发现,截图里多了一个求和结果,也就是说多运行了一次a。
原因在于,在额外的函数test_time(或者说是b)中, 多了一个原本没有的'func()',这个就导致了a函数多运行了一次。
在 a = time_test(a) 的时候,我们是要将time_test(a)赋给a这个变量以达成'运行额外的函数'这个目的的,但是现在我们因为在这个过程中函数b运行了一次,所以原来没有的func()让a函数多跑了一次
不过即使如此,我们还是离装饰器很近了,只要我们把这个多出来的运行给去掉,就可以说基本是大功告成了


4.函数嵌套和闭包:
再运用函数嵌套的知识,对上面的例子进行一些修改,就可以得到以下:
def time_test(func):
    def wrapper():
	import time
	start_time = time.time()
	func()
	end_time = time.time()
	result = end_time - start_time
	print('运行的时间为', result)
    return wrapper
		
#@time_test
def a():  	# 要被统计的函数
	x = sum([i for i in range(1000000)])
	print(x)

a = time_test(a)
a()		
在这里,我们又定义了一个函数wrapper(),并且将原来函数的内容都挪到这个新函数里去了,而且,我们还将time_test函数的返回值设成了wrapper。
也就是说,我们在运行a = time_test(a)的时候, 因为现在的这个time_test函数没有内容(内容都挪到子函数里去了),所以多出来的func()自然就没有了,也就是说a = test_time(a)里实际上存的是test_time()的子函数test_time2的地址【可能有人会问这不是子函数吗,他不是只在父函数里生效的吗,因为这个子函数已经作为返回值输出了,所以他可以被外界引用】。
那我们在运行a()【你可以认为运行的是time_test(a)()】的时候,实际上就是在运行test_time2这个函数,当它运行到func()这行时,因为我们并没有给wrapper这个函数传实参,当它在自身找不到这个func()时回到父函数中去找,于是就找到了a()函数。
另外呢,可以把a = time_test(a)这一步用@time_test这个python函数的语法糖来代替。这个“@+函数名”要放在被装饰的函数前面,这里的@time_test就相当于a = time_test(a),他们是一样的。

5.在简单的函数装饰器完成之后
a.在此,虽然我们完成了装饰器,但是通过分析上面的代码,我们会发现a函数的返回值没有了,他在wrapper()中并没有设置返回值,于是我们要给a函数(被装饰的函数)添加返回值
就是在func()这行加一个变量接收a函数的返回值再通过wrapper()的返回值将a函数的返回值传出来:
def time_test(func):
	def wrapper():
		import time
		start_time = time.time()
		jieguo = func()
		end_time = time.time()
		result = end_time - start_time
		print('运行的时间为', result)
	return jieguo
return wrapper



b.紧接着,我们还要进行最后的添加,因为装饰器是装饰不同函数的,所以当我们的被装饰函数有传入的值的时候,因为我们现在写的修饰器函数没有形参,所以会报错
所以我们要给装饰器加上形参以便接收不同函数的不同的形参:
def time_test(func):
	def time_test2(*args,**kwargs):
		import time
		start_time = time.time()
		func(*args,**kwargs)
		end_time = time.time()
		result = end_time - start_time
		print('运行的时间为', result)
return time_test2
				
这样,最终我们就完成了一个简单的装饰器

猜你喜欢

转载自blog.csdn.net/cApturE2F/article/details/80564967
今日推荐