簡體   English   中英

遞歸Python函數上的值修飾器

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM