繁体   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