從代碼計時想到的Python計時模塊、上下文管理器、變量生存期

Setup

前一篇文章中使用了代碼計時的功能,使用time模塊獲得起始和終止時間然后相減獲得運(yùn)行時間。

import time

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

Python中除了這種最原始的計時方案,還提供了專門的計時模塊timeit,該模塊提供了較為完善的計時功能,而且支持重復(fù)測試,更多的教程中提倡使用這種方案。以下例子來自博客

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)

以上是兩種較為常見的計時功能,有相應(yīng)的優(yōu)缺點(diǎn):

  • 第一種方案較為直觀,需要了解time模塊的使用方法,并且需要進(jìn)行單位轉(zhuǎn)換
  • 第二種方案有官方支持,但是不方便用于測量非函數(shù)代碼塊(例如某幾行代碼的運(yùn)行時間),而且需要通過參數(shù)來配置測試環(huán)境

基于以上考慮,我想能不能實(shí)現(xiàn)一種更加靈活的代碼計時工具。

上下文管理器 (Context Manager)

初見這個概念可能有點(diǎn)陌生,但下面的代碼大家一定再熟悉不過了:

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

上下文管理器是Python 2.5之后引入的概念,也就是常用的with語句。上下文管理器大大減輕了操作一些需要打開和關(guān)閉的資源(這里找不到好的定語了,就是文件和數(shù)據(jù)庫這種資源)的代碼。更重要的一點(diǎn)是,with語句使用很靈活,內(nèi)部可以包含任意的代碼。
實(shí)現(xiàn)上下文管理器有兩種方法,分別是在自定義類中實(shí)現(xiàn)__enter()__ __exit()__和使用裝飾器@contextlib.contextmanager

變量生存期

Python對于變量生存期的處理很神奇,這里不討論太多的內(nèi)容,只指出兩點(diǎn)較為特別的地方:

  • 上例中content是在with中定義的變量,但是在外部依舊可以使用
  • Python中的循環(huán)變量在循環(huán)結(jié)束之后依舊可以訪問到:
for i in range(10):
    pass
print(i) # 9

timeme

基于以上討論,實(shí)現(xiàn)一個用于代碼計時的上下文管理器,代碼已托管到gist,之后只在gist更新,文中代碼不再更新。
這個模塊僅僅用于學(xué)習(xí)上下文管理器和代碼計時,不恰當(dāng)?shù)牡胤綒g迎大家指正。

__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)

實(shí)現(xiàn)了基本的時間單位設(shè)置、精確位數(shù)設(shè)置,并提供了默認(rèn)的輸出字符串。

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

在一些簡單的計時任務(wù)中,相比于使用timeit模塊更加靈活而且無需手動配置運(yùn)行環(huán)境;相比于使用手動計時減少了代碼行數(shù)。

Teardown

  • 前幾天閑得無聊寫了這個模塊,很多都是得益于Python本身機(jī)制的便利。
  • 還有很多值得改善的地方,例如重復(fù)測試timeit.repeat()的功能支持。
  • 在代碼代碼計時、運(yùn)行效率瓶頸檢測方面,Python有很多完善的Profiler工具提供了相當(dāng)強(qiáng)大的功能,我對這方面還不是很熟悉,最近打算學(xué)習(xí)一下。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容