[英]Python decorator to time recursive functions
我有一個簡單的裝飾器來跟蹤 function 調用的運行時間:
def timed(f):
def caller(*args):
start = time.time()
res = f(*args)
end = time.time()
return res, end - start
return caller
這可以如下使用,並返回 function 結果和執行時間的元組。
@timed
def test(n):
for _ in range(n):
pass
return 0
print(test(900)) # prints (0, 2.69e-05)
很簡單。 但現在我想將其應用於遞歸函數。 正如預期的那樣,將上述包裝器應用於遞歸 function 會生成帶有每個遞歸調用時間的嵌套元組。
@timed
def rec(n):
if n:
return rec(n - 1)
else:
return 0
print(rec(3)) # Prints ((((0, 1.90e-06), 8.10e-06), 1.28e-05), 1.90e-05)
編寫裝飾器以便正確處理遞歸的優雅方式是什么? 顯然,如果是定時 function,您可以結束通話:
@timed
def wrapper():
return rec(3)
這將給出結果和時間的元組,但我希望所有這些都由裝飾器處理,以便調用者不必擔心為每次調用定義一個新的 function。 想法?
這里的問題不是裝飾者。 問題是rec
需要rec
成為一種行為方式的函數,但是你希望rec
是一個行為不同的函數。 沒有干凈的方法來協調單個rec
函數。
最干凈的選擇是停止要求rec
同時做兩件事。 而不是使用裝飾符號,將timed(rec)
分配給另一個名稱:
def rec(n):
...
timed_rec = timed(rec)
如果您不想要兩個名稱,則需要編寫rec
以了解裝飾的rec
將返回的實際值。 例如,
@timed
def rec(n):
if n:
val, runtime = rec(n-1)
return val
else:
return 0
到目前為止,我更喜歡其他答案(特別是user2357112的答案 ),但您也可以創建一個基於類的裝飾器來檢測該功能是否已被激活,如果是,則繞過時間:
import time
class fancy_timed(object):
def __init__(self, f):
self.f = f
self.active = False
def __call__(self, *args):
if self.active:
return self.f(*args)
start = time.time()
self.active = True
res = self.f(*args)
end = time.time()
self.active = False
return res, end - start
@fancy_timed
def rec(n):
if n:
time.sleep(0.01)
return rec(n - 1)
else:
return 0
print(rec(3))
(用(object)
編寫的類,以便與py2k和py3k兼容)。
請注意,要真正正常工作,最外層的調用應該使用try
和finally
。 這是__call__
幻想版本:
def __call__(self, *args):
if self.active:
return self.f(*args)
try:
start = time.time()
self.active = True
res = self.f(*args)
end = time.time()
return res, end - start
finally:
self.active = False
您可以通過* ahem *以不同的方式構建您的計時器*濫用contextmanager
和function attribute
...
from contextlib import contextmanager
import time
@contextmanager
def timed(func):
timed.start = time.time()
try:
yield func
finally:
timed.duration = time.time() - timed.start
def test(n):
for _ in range(n):
pass
return n
def rec(n):
if n:
time.sleep(0.05) # extra delay to notice the difference
return rec(n - 1)
else:
return n
with timed(rec) as r:
print(t(10))
print(t(20))
print(timed.duration)
with timed(test) as t:
print(t(555555))
print(t(666666))
print(timed.duration)
結果:
# recursive
0
0
1.5130000114440918
# non-recursive
555555
666666
0.053999900817871094
如果這被認為是一個糟糕的黑客,我會很樂意接受你的批評。
雖然它不是整合遞歸與裝飾器問題的整體解決方案,但對於時間問題,我已經驗證了時間元組的最后一個元素是整體運行時間,因為這是從上層開始的時間 - 最近的遞歸調用。 因此,如果你有
@timed
def rec():
...
給出你可以簡單做的原始函數定義來獲得整個運行時
rec()[1]
另一方面,獲取調用的結果將需要通過嵌套元組重用:
def get(tup):
if isinstance(tup, tuple):
return get(tup[0])
else:
return tup
這可能太復雜了,無法簡單地獲得您的函數的結果。
在嘗試分析簡單的quicksort
實現時,我遇到了同樣的問題。
主要問題是裝飾器在每個 function 調用上執行,我們需要一些可以保留 state 的東西,所以我們可以在最后總結所有調用。 裝飾器不是工作的正確工具
然而,一種想法是濫用函數是對象並且可以具有屬性的事實。 下面用一個簡單的裝飾器對此進行探討。 必須了解的是,通過使用裝飾器的 sintax 糖 ( @
),function 將始終累積其時序。
from typing import Any, Callable
from time import perf_counter
class timeit:
def __init__(self, func: Callable) -> None:
self.func = func
self.timed = []
def __call__(self, *args: Any, **kwds: Any) -> Any:
start = perf_counter()
res = self.func(*args, **kwds)
end = perf_counter()
self.timed.append(end - start)
return res
# usage
@timeit
def rec(n):
...
if __name__ == "__main__":
result = rec(4) # rec result
print(f"Took {rec.timed:.2f} seconds")
# Out: Took 3.39 seconds
result = rec(4) # rec result
# timings between calls are accumulated
# Out: Took 6.78 seconds
這給我們帶來了一個受@r.ook啟發的解決方案,下面是一個簡單的上下文管理器,它存儲每個運行時間並在最后打印其總和( __exit__
)。 請注意,因為對於每個時間我們都需要一個with
語句,所以這不會累積不同的運行。
from typing import Any, Callable
from time import perf_counter
class timeit:
def __init__(self, func: Callable) -> None:
self.func = func
self.timed = []
def __call__(self, *args: Any, **kwds: Any) -> Any:
start = perf_counter()
res = self.func(*args, **kwds)
end = perf_counter()
self.timed.append(end - start)
return res
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
# TODO: report `exc_*` if an exception get raised
print(f"Took {sum(self.timed):.2f} seconds")
return
# usage
def rec(n):
...
if __name__ == "__main__":
with timeit(rec) as f:
result = f(a) # rec result
# Out: Took 3.39 seconds
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.