从代码计时想到的Python计时模块、上下文管理器、变量生存期
admin
2023-07-30 20:43:17
0

Setup

前一篇文章中使用了代码计时的功能,使用time模块获得起始和终止时间然后相减获得运行时间。

import time

start = time.time()
# code to run
end = time.time()
cost = end - start #time in second

Python中除了这种最原始的计时方案,还提供了专门的计时模块timeit,该模块提供了较为完善的计时功能,而且支持重复测试,更多的教程中提倡使用这种方案。以下例子来自博客

def test1():
    n=0
    for i in range(101):
        n+=i
    return n

def test2():
    return sum(range(101))

def test3():
    return sum(x for x in range(101))

if __name__==\'__main__\':
    from timeit import Timer
    t1=Timer(\"test1()\",\"from __main__ import test1\")
    t2=Timer(\"test2()\",\"from __main__ import test2\")
    t3=Timer(\"test3()\",\"from __main__ import test3\")
    print t1.timeit(1000000)
    print t2.timeit(1000000)
    print t3.timeit(1000000)
    print t1.repeat(3,1000000)
    print t2.repeat(3,1000000)
    print t3.repeat(3,1000000)

以上是两种较为常见的计时功能,有相应的优缺点:

  • 第一种方案较为直观,需要了解time模块的使用方法,并且需要进行单位转换
  • 第二种方案有官方支持,但是不方便用于测量非函数代码块(例如某几行代码的运行时间),而且需要通过参数来配置测试环境

基于以上考虑,我想能不能实现一种更加灵活的代码计时工具。

上下文管理器 (Context Manager)

初见这个概念可能有点陌生,但下面的代码大家一定再熟悉不过了:

with open(\'afile.txt\', \'r\') as f:
    content = f.read()

上下文管理器是Python 2.5之后引入的概念,也就是常用的with语句。上下文管理器大大减轻了操作一些需要打开和关闭的资源(这里找不到好的定语了,就是文件和数据库这种资源)的代码。更重要的一点是,with语句使用很灵活,内部可以包含任意的代码。
实现上下文管理器有两种方法,分别是在自定义类中实现__enter()__ __exit()__和使用装饰器@contextlib.contextmanager

变量生存期

Python对于变量生存期的处理很神奇,这里不讨论太多的内容,只指出两点较为特别的地方:

  • 上例中content是在with中定义的变量,但是在外部依旧可以使用
  • Python中的循环变量在循环结束之后依旧可以访问到:
for i in range(10):
    pass
print(i) # 9

timeme

基于以上讨论,实现一个用于代码计时的上下文管理器,代码已托管到gist,之后只在gist更新,文中代码不再更新。
这个模块仅仅用于学习上下文管理器和代码计时,不恰当的地方欢迎大家指正。

__author__ = \'mengpeng\'
import time


class timeme(object):
    __unitfactor = {\'s\': 1,
                    \'ms\': 1000,
                    \'us\': 1000000}

    def __init__(self, unit=\'s\', precision=4):
        self.start = None
        self.end = None
        self.total = 0
        self.unit = unit
        self.precision = precision

    def __enter__(self):
        if self.unit not in timeme.__unitfactor:
            raise KeyError(\'Unsupported time unit.\')
        self.start = time.time()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.end = time.time()
        self.total = (self.end - self.start) * timeme.__unitfactor[self.unit]
        self.total = round(self.total, self.precision)

    def __str__(self):
        return \'Running time is {0}{1}\'.format(self.total, self.unit)

实现了基本的时间单位设置、精确位数设置,并提供了默认的输出字符串。

from timeme import timeme
with timeme(\'ms\', 6) as t:
    result = sum(range(100000))
print(r) # 4999950000
print(t.total) # 5.503178
print(t) # Running time is 5.503178ms

在一些简单的计时任务中,相比于使用timeit模块更加灵活而且无需手动配置运行环境;相比于使用手动计时减少了代码行数。

Teardown

  • 前几天闲得无聊写了这个模块,很多都是得益于Python本身机制的便利。
  • 还有很多值得改善的地方,例如重复测试timeit.repeat()的功能支持。
  • 在代码代码计时、运行效率瓶颈检测方面,Python有很多完善的Profiler工具提供了相当强大的功能,我对这方面还不是很熟悉,最近打算学习一下。

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
python清除字符串里非数字... 本文实例讲述了python清除字符串里非数字字符的方法。分享给大家供大家参考。具体如下: impor...