[英]Value-modifying decorator on recursive Python function
我正在嘗試在Python中創建一個計時裝飾器。 它是這樣的:
def function_timer(f):
"""
Creates an function timer wrapper around a function.
This is meant to be used as a decorator (see PEP 318).
Input:
f :: any function
Output:
A function that, when called, executes f with any arguments, while timing
the execution. The return value of this function depends on the return
value of f. If f returns
None ::
then this function returns None
a dictionary d ::
then this function returns a new dictionary with all the contents of
d, but with an additional key 'time' equal to the execution time of
the function. If the key 'time' is taken in the original dictionary,
then 'time_' will be tried, then 'time__', etc., until a free key is
found. The original dictionary will be shallow-copied, not modified.
some non-dict value x:
then this function returns a dictionary whose 'result' key gives x,
and whose 'time' key gives the execution time of the function.
"""
def timed_function(*args, **kwargs):
start_time = time.time()
print args, kwargs
result = f(*args, **kwargs)
end_time = time.time()
total_time = end_time - start_time
if result is None:
return None
elif type(result) >= dict:
result = result.copy()
key = 'time'
while key in result:
key += '_'
result[key] = total_time
else:
return { 'result': result, 'time': total_time }
return timed_function
這適用於簡單的功能。 但是當我在非記憶斐波那契等函數上嘗試時
@function_timer
def fib(n):
if n < 2:
return 1
else:
return fib(n - 2) + fib(n - 1)
它失敗,因為隨着遞歸:
fib(0)
和fib(1)
返回1
{ 'result': 1, 'time': 0.001 }
fib(2)
嘗試添加fib(0) + fib(1)
,即
{ 'result': 1, 'time': 0.001 } + { 'result': 1, 'time': 0.001 }
這失敗了。
我怎樣才能得到一個裝飾器只裝飾一個函數的最終返回值? 我根本不需要修改fib
函數。
如果使用function_timer
函數作為裝飾器,它將為您不需要的fib
函數的每次調用計時。 您可能想計時所有Fibonacci函數執行所需的累積時間。
如@minitech所建議,您可以通過function_timer
函數簡單地包裝fibonacci函數。
start_time = time.time()
result = fib(*args, **kwargs)
end_time = time.time()
上面的第二行僅在所有遞歸完成后才返回,並且end_time - start_time
將准確反映執行時間。
您可能沒有意識到這一點,但是裝飾器f
封裝了函數g
如下所示: f(g(x))
。 在您的情況下,您不想為每個調用都用f
包裝g
。 您只想要一次,因此裝飾器在這里不是很有用。
timed_fib = function_timer(fib)
試圖讓fib
為遞歸調用提供一種行為,為函數的其他用戶提供另一種行為是痛苦的秘訣。 只是不要替換fib
。 使用其他名稱作為計時功能。 如果需要,可以將fib
重命名為_fib
,並將定時版本設置為fib
:
fib = function_timer(_fib)
未來會有黑客入侵。 請謹慎操作。
由於cPython使我們能夠訪問框架自省功能,因此您可以使用它來嘗試確定給定的調用是否遞歸。 基本上:如果我們調用f
,我們可以窺視堆棧中的下一幀; 如果它得到了相同的__name__
為f
,它可能是一個遞歸調用的結果。 大概。
import sys
def function_timer(f):
def timed_function(*args, **kwargs):
if f.__name__ == sys._getframe(1).f_code.co_name:
# recursive call! Probably!
return f(*args, **kwargs)
# no changes to your function below here
start_time = time.time()
result = f(*args, **kwargs)
end_time = time.time()
total_time = end_time - start_time
if result is None:
return None
elif type(result) >= dict:
result = result.copy()
key = 'time'
while key in result:
key += '_'
result[key] = total_time
else:
return { 'result': result, 'time': total_time }
return timed_function
因此:
@function_timer
def fib(n):
if n < 2:
return 1
else:
return fib(n - 2) + fib(n - 1)
fib(2)
Out[60]: {'result': 2, 'time': 2.86102294921875e-06}
fib(20)
Out[61]: {'result': 10946, 'time': 0.03364205360412598}
免責聲明部分:這很脆弱。 不要以任何類似於生產代碼的方式執行此操作-或(如果必須)提供比if f.__name__ == sys._getframe(1).f_code.co_name:
更為嚴格的測試方法 。 絕對不要期望這可以移植到python的所有實現中。
綜上所述,進入堆棧框架基本上是判斷給定函數調用是否遞歸的唯一方法。 或者,好吧,從技術上講,還有另一種方法來對函數的__dict__
進行注釋,但這是一個更脆弱的數量級,因此就可以了。 (或者干脆不使用裝飾器,如其他答案所述)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.