Python功能點實現:函式級/程式碼塊級計時器
工程中我們常常需要對某一個函式或者一塊程式碼計時,從而監測系統關鍵位置的效能。計時方法是在程式碼塊前後分別記錄當前系統時間,然後兩者相減得到程式碼塊的耗時。最簡單原始的實現類似:
from datetime import datetime
start = datetime.now()
# some code you want to measure
end = datetime.now()
print("Processing time for {} is: {} seconds".format('You Name It', elapse))
這種方式缺點明顯:假如系統內有很多地方都需要計時,那麼每個地方都需要插入這樣的計時程式碼,首先是重複性工作很麻煩,其次這樣會降低程式碼的可讀性,干擾對業務邏輯的理解。本文將給出一些更好的實現,主要涉及的技術是裝飾器(Decorator)
基於裝飾器的函式級計時器
第一種計時器是比較常見的函式級計時器,通過裝飾器完成,將原函式改裝成擁有計時功能的新函式,使其可以完成執行原來函式和計時兩件事。在使用時,只用在需要計時功能的函式程式碼前加上類似@timer
的語法糖,這樣每次呼叫原函式時,執行的將會是新函式。這樣就大大減少了重複性勞動。
具體實現如下:
from datetime import datetime def timer(func): '''Function Level Timer via Decorator''' def timed(*args, **kwargs): start = datetime.now() result = func(*args, **kwargs) end = datetime.now() elapse = (end - start).total_seconds() print("Processing time for {} is: {} seconds".format(func.__name__, elapse)) return result return timed @timer def test_1(a): '''Function Level''' a *= 2 return a if __name__ == '__main__': print(test_1(1))
基於上下文的程式碼塊級計時器
裝飾器實現的計時器可以為函式新增計時功能,可以滿足大部分情況的需要,但是假如我們想要更靈活一些,對任意一段連續的程式碼塊做計時,怎樣做?使用原始的插計時程式碼的方法顯然不是我們想要的;也可以將程式碼塊重構成一個函式,再在上面加裝飾器,然而這就顯得不夠優雅。因此我做出了下面的實現。
首先了解上下文管理的概念。大致是說Python中允許建立一種叫上下文管理器(Context Manager)的物件,它可以管理一個程式碼塊執行時的上下文資訊。具體的方法是建立一個類,併為其實現object.__enter__
和object.__exit__
方法,前者在進入程式碼塊時自動執行,後者在完成程式碼塊執行時自動執行。
在使用時,通過with
和as
關鍵字,將__enter__
的返回值繫結到某一個變數名,這個返回值裡可以儲存程式碼塊執行過程中得到的一些資訊,在這裡就是執行時間啦。具體的實現是建立一個計時器類Timer
,在enter
時記錄程式碼塊執行的開始時間,exit
時記錄完成時間、計算並儲存耗時到Timer
例項中。在使用時,將with Timer() as t
加到要計時的程式碼塊前面,t.elapse
中將會儲存程式碼塊耗時,可以任意使用。
在這個基礎上,我們還可以做出一個裝飾器timer_
來實現基於上下文的函式級計時器。
具體實現如下:
class Timer(object):
'''Code Block Level Timer via Context'''
def __enter__(self):
self.start = datetime.now()
return self
def __exit__(self, *args):
self.end = datetime.now()
self.elapse = (self.end - self.start).total_seconds()
def timer_(func):
'''Function Level Timer via Context & with Statement'''
def timed(*args, **kw):
with Timer() as t:
result = func(*args, **kw)
print("Processing time for {} is: {} seconds".format(func.__name__, t.elapse))
return result
return timed
def test_2(a):
'''Code Block Level'''
with Timer() as t:
a *= 2
print("Processing time for {} is: {} seconds".format('You Name It', t.elapse))
return a
@timer_
def test_3(a):
'''Function Level'''
a *= 2
return a
if __name__ == '__main__':
print(test_2(2))
print(test_3(3))
更靈活的實現
更優雅地,我們還可以使用contextlib
自帶的ContextDecorator
,參考官方示例,做出既可以with
又可以作為裝飾器
的計時器timer_elegant
:
from datetime import datetime
from contextlib import ContextDecorator
class timer_elegant(ContextDecorator):
'''Elegant Timer via ContextDecorator'''
def __init__(self, name):
self.name = name
def __enter__(self):
self.start = datetime.now()
def __exit__(self, *args):
self.end = datetime.now()
self.elapse = (self.end - self.start).total_seconds()
print("Processing time for {} is: {} seconds".format(self.name, self.elapse))
@timer_elegant('test_4')
def test_4(a):
a *= 2
return a
def test_5(a):
a *= 2
return a
if __name__ == '__main__':
print(test_4(4))
with timer_elegant('test 5'):
result_5 = test_5(5)
print(result_5)