在安卓審計項目裡,我們使用python腳本來對APK進行掃描檢測,為了統計每一步操作的耗時情況編寫了一個簡單的類,并通過下面的方式使用:
#coding:utf-8
import time
class TimeRecorder:
def __init__(self, name):
print(name + u"開始")
self.name = name
self.startTime = time.time()
def __del__(self):
print(u"{0}結束,耗時:{1}".format(self.name, time.time() - self.startTime))
def scan1():
t = TimeRecorder(scan1.func_name)
time.sleep(1)
return 1
print scan1()
運作後列印輸出: scan1開始 scan1結束,耗時:1.0 1
主要利用了類的構造函數和異構函數,實際上在scan1函數裡建立的 TimeRecorder對象并沒有真實使用,需要統計耗時的函數開始都要聲明一個 TimeRecorder 對象,雖然也比較簡單,但是使用者有時會莫名其妙。 在學習了python的裝飾器後,便有了更好的設計,讓裝飾器來統一調用即可,實作方式如下:
# 函數裝飾器,讓函數列印耗時
def deco(func):
def _deco():
t = TimeRecorder(func.func_name)
return func()
return _deco
@deco
def scan2():
time.sleep(1)
return 2
print scan2()
TimeRecorder 對象不變,使用時調用者函數代碼不變,隻需要在前面那個@裝飾器即可。 運作輸出:
scan2開始 scan2結束,耗時:1.0 2
Python裝飾器(decorator)在實作的時候,有一些細節需要被注意。例如,被裝飾後的函數其實已經是另外一個函數了(函數名等函數屬性會發生改變)。這樣有時候會對程式造成一些不便,例如筆者想對unittest架構中的一些函數添加自定義的decorator,添加後由于函數名和函數的doc發生了改變,對測試結果有一些影響。
是以,Python的functools包中提供了一個叫wraps的decorator來消除這樣的副作用。寫一個decorator的時候,最好在實作之前加上functools的wrap,它能保留原有函數的名稱和docstring。
在上面的例子中,如果列印scan2的函數名:
print scan2.func_name
會發現輸出的是_deco 這顯然會影響開發者的使用習慣和判斷,怎麼辦呢?
這個時候就需要functools.wraps
from functools import wraps
def decorator2(func):
@wraps(func)
def wrapper(*args, **kwargs):
t = TimeRecorder(func.func_name)
return func(*args, **kwargs)
return wrapper
@decorator2
def scan3():
u"""掃描APK的權限。。。。"""
time.sleep(1)
return 3
print scan3.func_name, scan3.func_doc
print scan3()
運作輸出:
scan3 掃描APK的權限。。。。
scan3開始
scan3結束,耗時:0.0
3
這個時候函數名和文檔描述就是正确的了。
2017.02.20更新
"""
時間記錄的函數和裝飾器
"""
class TimeRecorder:
def __init__(self, name):
print(name + u" start")
self.name = name
self.startTime = time.time()
def __del__(self):
print(u"%s end, time used: %.1f s", self.name, time.time() - self.startTime)
"""
def scan1():
t = TimeRecorder(scan1.func_name)
time.sleep(1)
return 1
print scan1()
"""
# 函數裝飾器,讓函數列印耗時
def logtime(func):
def wrapper(*args, **kwargs):
print(func.func_name + u" start")
startTime = time.time()
ret = func(*args, **kwargs)
print(u"%s end, time used: %.1f s", func.func_name, time.time() - startTime)
return ret
return wrapper
# 指定一個名稱
def logtimewithname(name = None):
def wrapper(func):
def wrapper2(*args, **kwargs):
_name = name
if name is None:
_name = func.func_name
else:
_name = name
print(_name + u" start")
startTime = time.time()
res = func(*args, **kwargs)
print(u"%s end, time used: %.1f s", _name, time.time() - startTime)
return res
return wrapper2
return wrapper
用法:
@logtime
def scan1(p1, p2):
time.sleep(1)
return 1
print scan1(1, 2)
# @logtimewithname()
@logtimewithname(u"掃描")
def scan2(p1, p2):
time.sleep(1)
return 2
print scan2(1, 2)