天天看點

Python列印函數用時的兩種方法--類的構造析構函數與函數裝飾器2017.02.20更新

在安卓審計項目裡,我們使用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)