时间递归函数的Python装饰器

2024-04-23 13:35:41 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个简单的decorator来跟踪函数调用的运行时:

def timed(f):
    def caller(*args):
        start = time.time()
        res = f(*args)
        end = time.time()
        return res, end - start
    return caller

这可以如下使用,并返回函数结果和执行时间的元组。在

^{pr2}$

很简单。但现在我想把它应用到递归函数中。如预期的那样,将上述包装器应用于递归函数将生成嵌套元组,其中包含每次递归调用的次数。在

@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)

编写decorator使其能够正确处理递归的优雅方式是什么?显然,如果定时函数:

@timed
def wrapper():
    return rec(3)

这将给出结果和时间的元组,但我希望所有这些都由decorator处理,这样调用者就不必为每次调用定义一个新函数而担心。思想?在


Tags: 函数returntimedef时间argsresdecorator
3条回答

你可以用一种不同的方式来构造你的计时器,通过一点滥用contextmanager和{}一点。。。在

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)

结果:

^{pr2}$

如果这被认为是一次糟糕的黑客攻击,我很乐意接受你的批评。在

到目前为止,我更喜欢其他答案(特别是user2357112's answer),但您也可以创建一个基于类的decorator,它可以检测函数是否已被激活,如果激活了,则绕过计时:

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和{}。以下是__call__的奇幻版本:

^{pr2}$

这里的问题不是真正的装饰师。问题是rec需要rec是一个行为方式不同的函数,但是您希望{}成为一个行为不同的函数。没有一种干净的方法可以将其与单个rec函数协调起来。在

最干净的选择是停止要求rec同时是两个东西。不要使用装饰符号,而是将timed(rec)指定给其他名称:

def rec(n):
    ...

timed_rec = timed(rec)

如果不需要两个名称,则需要编写rec,以了解修饰后的rec将返回的实际值。例如

^{pr2}$

相关问题 更多 >