Shawn's Blog
目录 · 4 节

Python 装饰器

0X00 给一个方法计时

现在我们有一个需求,需要给程序中的一部分方法计时,以监控他们执行完具体用了多久。那么在没有装饰器的情况下我们会写出类似这样的代码:

python
1import datetime
2
3def foo():
4    pass    # some code
5
6
7start = datetime.datetime.now()
8foo()
9end = datetime.datetime.now()
10print((end - start).seconds)

0X01 引入装饰器

如果只是临时给一个方法使用也不是不行,但是如果我们需要监控大量的方法呢?众所周知Python中function也是可以作为参数传递的,那来看一下下面这种写法呢

python
1import datetime
2
3
4def timer(func):
5    def run_func(): # 方法内部将之前计时的功能封装起来
6        start = datetime.datetime.now()
7        result = func() # 得到参数方法的返回值
8        end = datetime.datetime.now()
9        print((end - start).seconds)
10        return result   # 将真正的结果返回给调用者
11
12    return run_func   # 调用内部方法,进行计时
13
14
15def foo():
16    pass    # some code
17
18
19def bar():
20    pass    # some code
21
22
23# 将方法作为参数发送给 timer()
24timer(foo)()
25timer(bar)()

因为上面timer(foo)返回的结果是一个func,所以后面需要再加一对括号来调用这个方法

这种方法只写了一次计时的逻辑,但是可以给任意一个方法使用,其实这时候def timer(func)已经是装饰器了,下面调用的方法timer(foo)/timer(bar)也是正确的装饰器使用方法。那是不是觉得和常见的装饰器使用不太一样呢?其实常见的@timer用法是Python中提供的一种语法糖。

0X02 @语法糖

其实上面的代码就可以直接使用@语法糖了,具体用法是这样:

python
1@timer
2def foo():
3    pass    # some code
4
5
6foo()

语法糖实际只是便于我们编码的一种设计,按理说一切被称为语法糖的东西都不是必须的。比如说这里的语法糖完全可以用上面的方法来应用装饰器,但是为什么还是设计了这个语法糖呢?我们对比下面两种方法的调用,我们假设get_data_from_server/db是一个耗时较久的方法:

python
1def get_data_from_server():
2    pass
3
4
5@timer
6def get_data_from_db():
7    pass
8
9
10timer(get_data_from_server))()
11
12get_data_from_db()

这两种方法看起来明显是后面get_data_from_db的用法更易读。所以这处语法糖的出现能大幅度的提升代码的可读性,更能提升维护性质:当代码中调用了1W次非语法糖形式的装饰器计时时,取消计时就要修改1W行代码;如果用了@语法糖,那就只需要将方法定义处的@timer注释掉就可以了。

0X03 传参

“那要传参咋搞哇?” “这么搞”

python
1#!/usr/bin/env python
2# coding=utf-8
3
4import time
5import datetime
6
7
8def timer(func):
9    def run_func(secs): # 这儿接受参数
10        start = datetime.datetime.now()
11        result = func(secs) # 这儿把参数搞进去
12        end = datetime.datetime.now()
13        print((end - start).microseconds)
14        return result
15
16    return run_func
17
18
19@timer
20def foo(secs):
21    time.sleep(secs)
22
23
24@timer
25def bar(secs):
26    time.sleep(secs)
27
28
29if __name__ == '__main__':
30    foo(3)  # 和不使用@timer时候的timer(foo)(3)是一样的
31    bar(5)  # 和不使用@timer时候的timer(bar)(5)是一样的

“那我要是有好几个参数呢,怎么搞?” “就还是这么搞啊”

python
1def timer(func):
2    def run_func(*args): # 这儿接受参数,几个都行
3        start = datetime.datetime.now()
4        result = func(*args) # 这儿把参数搞进去,几个都行
5        end = datetime.datetime.now()
6        print((end - start).microseconds)
7        return result
8
9    return run_func
10
11
12@timer
13def foo(start_secs, end_secs):
14    time.sleep(random.randint(start_secs, end_secs))
15
16
17foo(3, 5)
本文标题
Python 装饰器
文章作者
Shawn
版权声明
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

如果这篇文章对你有帮助,可以请我喝杯咖啡 ☕

评论