初解装饰器
装饰器
装饰器就是一个函数,用来装饰另一个函数。为另一个函数添加新功能。
特点:①不改变原来函数的源代码 ②不改变原来函数的调用方式
装饰器=高阶函数+嵌套函数+函数闭包
加入目前我有一个这样的test函数,想给它增加一个新的功能,就是查看函数的运行时间,那我该怎么做呢?
1 import time 2 def test(): 3 for i in range(5): 4 time.sleep(0.1) 5 print("运行完毕") 6 7 test()
第一种方法也是最直接的方法:直接改动源代码
1 import time 2 def test(): 3 start_time = time.time() 4 for i in range(5): 5 time.sleep(0.1) 6 print("运行完毕") 7 stop_time = time.time() 8 print("该函数运行的时间是%s"%(stop_time - start_time)) 9 10 test()
结果是:
运行完毕
该函数运行的时间是0.5019335746765137
这样改动的话看起来十分简单的操作
然而如果你的手边有10000或者是更多这样的函数,每一个都要这样添加函数功能,那你还会一个一个的去修改这些函数么?
答案是当然不会的啦,这时你就会想到另一个方法,我能不能再另外定义一个函数用来专门统计时间呢?当然可以了
1 import time 2 3 def timer(func): 4 start_time = time.time() 5 func() 6 stop_time = time.time() 7 print("该函数运行的时间是%s"%(stop_time - start_time)) 8 9 def test(): 10 for i in range(5): 11 time.sleep(0.1) 12 print("运行完毕") 13 14 timer(test)
结果:
运行完毕
该函数运行的时间是0.5027310848236084
这样一看是不是简单多了呢?
但是面你对成千上万的函数,每次都去这样调用是不是还是显得麻烦了呢?
假想一下:你调用函数
timer(test)
timer(test1)
timer(test2)
timer(test3)
.......
这样的话你又突然想到我把test函数的返回值写成return timer,然后定义一个test变量作为test()的返回值,再调用test()不就行了么?这样想一下,貌似是完美的。然而。。。
1 import time 2 3 def timer(): 4 start_time = time.time() 5 test() 6 stop_time = time.time() 7 print("该函数运行的时间是%s"%(stop_time - start_time)) 8 9 def test(): 10 for i in range(5): 11 time.sleep(0.1) 12 print("运行完毕") 13 return timer 14 test = test() #可以使用test()()的方式调用 15 test()
结果:
运行完毕
运行完毕
该函数运行的时间是0.5015039443969727
哇,结果是错的(调用了两次test()函数 ),天啊,而且好像又把它给搞的更加复杂了,那到底应该怎么做呢,真让人头皮发麻!
如果把定义的timer()放到另一个新定义的decorate()中然后decorate函数的返回值设置为timer是不是就可以了呢?来让我们试一下
1 import time 2 3 def decorate(func): 4 def timer(): 5 start_time = time.time() 6 func() 7 stop_time = time.time() 8 print("该函数运行的时间是%s"%(stop_time - start_time)) 9 return timer 10 def test(): 11 for i in range(5): 12 time.sleep(0.1) 13 print("运行完毕") 14 15 test = decorate(test) 16 test()
结果:
运行完毕
该函数运行的时间是0.5024490356445312
哇,完美。
然而在python中有一种语法糖就是我们常常用到的@符号
@装饰器函数就等价于(变量 = 装饰器函数(被装饰函数名字))
在这里既是@decorate = decorate(test)
怎么样,是不是很美妙呢?
让我们试一试是不是真的如我所说
1 import time 2 3 def decorate(func): 4 def timer(): 5 start_time = time.time() 6 func() 7 stop_time = time.time() 8 print("该函数运行的时间是%s"%(stop_time - start_time)) 9 return timer 10 11 @decorate 12 13 def test(): 14 for i in range(5): 15 time.sleep(0.1) 16 print("运行完毕") 17 18 test()
结果:
运行完毕
该函数运行的时间是0.5020065307617188
是了,语法糖@可用无疑
这样一个简单的装饰器就被我们创建出来了
但是新问题又来了,如果我的被装饰函数有返回值或者函数形参怎么办呢?
首先我们先看一下有函数返回值的情况怎么办?
import time def decorate(func): def timer(): start_time = time.time() res = func() #此处调用被修饰函数,在此处定义一个变量接受被装饰函数的返回值就可以了 stop_time = time.time() print("该函数运行的时间是%s"%(stop_time - start_time)) return res #此处返回函数的返回值 return timer @decorate def test(): for i in range(5): time.sleep(0.1) return "运行完毕" print(test())
结果:
该函数运行的时间是0.5053601264953613
运行完毕
看来这样是可以的,我们又解决了一个问题
接下来让我们看看函数有形参的情况下怎么办?
这时候我们就要使用*args, **kwargs这两个可变长参数了,因为我们传入参数数量是不确定的,比如一个函数需要两个参数,另一个需要三个参数,另一个需要4个参数。。。。。
好了,那这样我们该怎么写呢?
1 import time 2 3 def decorate(func): 4 def timer(*args, **kwargs): 5 start_time = time.time() 6 res = func(*args, **kwargs) 7 stop_time = time.time() 8 print("该函数运行的时间是%s"%(stop_time - start_time)) 9 return res 10 return timer 11 12 @decorate 13 14 def test(name, age): 15 for i in range(5): 16 time.sleep(0.1) 17 return "我的名字是%s,我的年龄是%s"%(name,age) 18 19 print(test("bob", 18))
结果:
该函数运行的时间是0.5026912689208984
我的名字是bob,我的年龄是18
这样一个装饰器的基本条件基本上是齐备了 。
最后一个问题,如果我想在每一个被修饰的函数前面天界一句“hello,world!”我该怎么办呢?
简单,就是在装饰函数外在套一个装饰器函数就好了,在这里我套了函数decorate1。
1 import time 2 def decorate1(s): 3 def decorate(func): 4 def timer(*args, **kwargs): 5 print("%s"%s) 6 start_time = time.time() 7 res = func(*args, **kwargs) 8 stop_time = time.time() 9 print("该函数运行的时间是%s"%(stop_time - start_time)) 10 return res 11 return timer 12 return decorate 13 @decorate1("hello,world!") 14 15 def test(name, age): 16 for i in range(5): 17 time.sleep(0.1) 18 return "我的名字是%s,我的年龄是%s"%(name,age) 19 20 print(test("bob", 18))
结果:
hello,world! 该函数运行的时间是0.5027992725372314 我的名字是bob,我的年龄是18
好了,关于装饰器,目前只能想到这么多了!!!