从一个需求说起

1
2
3
4
def test():
print('do sth...')

test()

现有如上函数,想加一个功能,算一下test函数执行的时间?
要求:不要改变原来的代码

针对上述需求,你可能会有如下思路

先有一个计算时间的函数,然后将test函数的调用放在开始时间和结束时间即可
如下代码

1
2
3
4
5
6
7
import time
def count_time():
start_time = time.time()
test()
end_time = time.time()
print("the current func time is %s" % (end_time - start_time))
count_time()

感觉这个需求非常的简单,寥寥几行就已经完成了,但如果这个时候有100多个函数呢,都需要计算,怎么办?难道写100遍?

为了解决上述的问题,我们想,我们之前不是学过参数吗?我能不能将这个函数名当做参数传入到count_time这个函数中去呢?
注意:函数是可以当做参数,传入到另一个函数中的

于是,天才的你写出了如下的代码

1
2
3
4
5
6
def count_time(func):
start_time = time.time()
func()
end_time = time.time()
print("the current func time is %s" % (end_time - start_time))
count_time(test) # 此时,我们将test函数传入到count_time()函数中

但是此时我们发现改变了函数的调用方式,之前是test()调用的,现在是count_time(test),感觉还是不符合之前的要求,于是你又陷入了沉思…

但是不要害怕,我们想,我们此时是不是只需要想办法将 test() == count_time(test) 是不是就可以了?

于是,为了实现调用test和调用count_time一样的效果,我们想能不能把计算时间的这个函数搞成一个嵌套函数,然后返回内层函数,最终赋给一个变量,恰好就叫test呢,此时这个test就是一个函数的内存地址

1
2
3
4
5
6
7
8
9
10
def count_time(func):
def deco():
start_time = time.time()
func()
end_time = time.time()
print("current func time is %s" % (end_time - start_time))
return deco
# 此时,调用test 就相当于调用了 count_time了
test = count_time(test)
test()

装饰器的写法

其实,上面就实现了一个装饰器的功能,只不过在python中,装饰器是有自己特定语法的,所以最终装饰器的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time
def count_time(func):
def warpper():
start_time = time.time()
func()
stop_time = time.time()
print('the func run time is %s' %(stop_time - start_time))
return warpper
# 下面就是装饰器的语法,使用@来表示
@count_time
def test():
time.sleep(1)
print('in the test')
test()

如果我想给test函数传入参数,那怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time
def count_time(func):
def warpper(*args,**kwargs):
start_time = time.time()
func(*args,**kwargs)
stop_time = time.time()
print('the func run time is %s' %(stop_time - start_time))
return warpper
# 下面就是装饰器的语法,使用@来表示
@count_time
def test(name):
time.sleep(1)
print('in the test')
test('zhangsan')

带参数的装饰器写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time
def count_time(level):
def decorate(func):
def warpper(*args,**kwargs):
print('the level is', level)
start_time = time.time()
func(*args,**kwargs)
stop_time = time.time()
print('the func run time is %s' %(stop_time - start_time))
return warpper
return decorate
# 下面就是装饰器的语法,使用@来表示
@count_time(level='warn')
def test(name):
time.sleep(1)
print('in the test')
test('zhangsan')

装饰器的定义

官方定义

本质上其实就是一个函数,可以让其他的函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数。

常见的使用场景:

  • 插入日志,性能测试,登录权限校验等
  • 概括的讲,装饰器就是为已经存在的对象添加额外的功能

形象比喻

所谓的装饰器,就好像冬天的长裤,我们知道内裤可以用来遮羞,但是到了冬天没法帮我们防风御寒,于是聪明的人们发明了长裤,它在不影响内裤作用的前提下,给我们的身子提供了保暖的功效