有时候我们想看看一个函数的执行时间是多久,这时候我们可以使用装饰器,在函数的执行开始前,记录一个时间,在函数的执行结束后记录一个时间,然后求两个数的差,就可以得到这个函数本次的执行时间了。但是这样做的做法,太Low,接下来我们就说说Python 内置的timeit 模块
timeit 模块可以用来测试一小段Python 代码的执行速度
timeit 模块里面有一个Timer 类,首先需要实例化这个类,先看一下初始化这个类所可以接收的参数,下面是timeit 模块关于Timer 的源码
1 class Timer: 2 ... 3 def __init__(self, stmt="pass", setup="pass", timer=default_timer, globals=None) 4 ...
资料显示这个是Python2.3 更新的一个完美计时工具,可以测量Python 代码的运行时间。
首先Timer 是这个测量代码执行时间的类
这个类实例化时可接收的参数
stmt:要测试的代码语句(statment)
setup:运行代码时需要的设置,也就是要测试代码的前置条件
timer:参数时一个定时器函数,与平台有关
globals:待查,后续更新
这个类实例出来后有两个方法是我们经常用到的
Timer.timeit(number=default_number)
参数number 是测试代码时的测试次数,默认default_number 为1000000 次,这个方法返回执行代码的耗时,一个float 类型的秒数。
Timer.repeat(repeat=default_repeat, number=default_number)
参数repeat 是重复整个测试的次数,默认是重复3 次,参数number 是每个测试代码时的测试次数,默认也是1000000 次,
这个方法返回一个列表,元素的个数就是重复整个测试的次数,每个元素值就是每个测试代码的耗时,是一个float 类型的秒数。
由简单着手,具体如下:
1 import timeit 2 # 创建timeit 对象 3 timer_hello = timeit.Timer("print('hello')) 4 # 调用timer 方法执行1000 次,并打印结果 5 time_use = time_hello.timeit(1000) 6 print('elapse :{}'.format(time_use)) 7 8 9 hello 10 ... 11 hello 12 elapse :0.07077193709161148
但是事实上没人会测试打印1000 次“hello”所用的时间,除非特别无聊,举这个例子只是为了方便直观的说明第二个参数的作用
1 import timeit 2 3 a = 'hello' 4 5 timer_a = timeit.Timer("print(a)") 6 time_use = time_a.timeit(1000) 7 print('elapse :{}'.format(time_use)) 8 9 10 ... 11 NameError: name 'a' is not defined
为什么会这样呢?明明已经定义了变量a 啊,为什么还是提示未定义?
这是因为这个类实例话的时候,会构建一个独立的虚拟空间用于测试待测试代码
只需要传入第二个参数就可以解决这个问题
1 import timeit 2 3 # a = 'hello' 4 5 timer_a = timeit.Timer("print(a)",“a='hello'”) 6 time_use = time_a.timeit(1000) 7 print('elapse :{}'.format(time_use)) 8 9 hello 10 ... 11 elapse :0.038286632634104326 12 # 时间上会比直接打印‘hello’要稍微长一点
这样就没问题了,可是我们当初的目的是用来打印一个函数啊,难不成要在第二个参数上写一个函数,比如匿名函数f = lambda :print(‘hello’)?
1 import timeit 2 3 # a = 'hello' 4 5 timer_a = timeit.Timer("f()",“f=lambda : print('hello')”) 6 time_use = time_a.timeit(1000) 7 print('elapse :{}'.format(time_use)) 8 9 hello 10 ... 11 elapse :0.014775986865789454
也没问题,但是这只是个简单的函数,如果函数的逻辑很复杂,代码量很多,这样就不太好用了,那怎么办呢?答案是导入,将当前模块导入到这个Timer 实例对象的环境中就行了
1 import timeit 2 3 # a = 'hello' 4 def print_hello(): 5 print('hello') 6 7 timer_a = timeit.Timer("print_hello()",“from __main__ import print_hello”) 8 time_use = time_a.timeit(1000) 9 print('elapse :{}'.format(time_use)) 10 11 12 hello 13 ... 14 elapse :0.038094632804770844
下面是Python 中list 的一些操作测试
1 def t1(): 2 l = [] 3 for i in range(1000): 4 l = l + [i] 5 def t2(): 6 l = [] 7 for i in range(1000): 8 l.append(i) 9 def t3(): 10 l = [i for i in range(1000)] 11 def t4(): 12 l = list(range(1000)) 13 14 import timeit 15 16 time_t1 = timeit.Timer('t1()', 'from __main__ import t1') 17 print('concat use {} seconds'.format(time_t1.timeit(1000))) 18 time_t2 = timeit.Timer('t2()', 'from __main__ import t2') 19 print('append use {} seconds'.format(time_t2.timeit(1000))) 20 time_t3 = timeit.Timer('t3()', 'from __main__ import t3') 21 print('comprehension use {} seconds'.format(time_t3.timeit(1000))) 22 time_t4 = timeit.Timer('t4()', 'from __main__ import t4') 23 print('list range use {} seconds'.format(time_t4.timeit(1000))) 24 25 concat use 1.1694602938130723 seconds 26 append use 0.0634083880814329 seconds 27 comprehension use 0.028811085501257327 seconds 28 list range use 0.009896880091662119 seconds
由此可以看出,Python 内置的list 函数强转的效率有多高,列表推导式仅次之,列表尾部添加元素的效率正常,而列表的拼接后重新引用的执行效率,则要差上很多了。
相对于append,我们看一下列表头部插入的执行效率
1 def t5(): 2 l = [] 3 for i in range(1000): 4 l.insert(0, i) 5 time_insert = timeit.Time('t5()', 'from __main__ import t5') 6 print('insert use {} seconds'.format(time_t5.timeit(1000))) 7 8 insert use 0.2727046464847587 seconds
可以看出列表都不插入的效率比尾部插入的效率要低的多
最后再来说一说repeat 方法
print('append information :{}'.format(time_t2.repeat(4, 1000))) print('insert information :{}'.format(time_t5.repeat(4, 1000))) append information :[0.06173994512004878, 0.06133461214701143, 0.06315461052923509, 0.06169727849130799] insert information :[0.26799709511369324, 0.26826598376357, 0.2789770853537018, 0.26663220743803784]
可以看出,对这些代码的运行,进行了输入个数的测试,并返回了一个列表,元素为每个测试的结果