Skip to main content
 首页 » 编程设计

python之当参数是递归函数时装饰器如何工作

2025年05月04日23lyj

import time 
def clock(func): 
   def clocked(*args): 
       t0 = time.perf_counter() 
       result = func(*args)  
       elapsed = time.perf_counter() - t0 
       name = func.__name__ 
       arg_str = ', '.join(repr(arg) for arg in args) 
       print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
       return result 
   return clocked 

这是装饰器。

@clock 
def factorial(n): 
    return 1 if n < 2 else n*factorial(n-1) 

结果部分是:

[0.00000191s] factorial(1) -> 1 
[0.00004911s] factorial(2) -> 2 
[0.00008488s] factorial(3) -> 6 
[0.00013208s] factorial(4) -> 24 
[0.00019193s] factorial(5) -> 120 
[0.00026107s] factorial(6) -> 720 
6! = 720 

当参数是递归函数时,这个装饰器是如何工作的?为什么装饰器可以执行多次。它是如何工作的?

请您参考如下方法:

在您的示例中,clock 装饰器执行一次,当它用时钟版本替换原始版本的 factorial 时。原始的 factorial 是递归的,因此修饰后的版本也是递归的。因此,您可以获得为每个递归调用打印的计时数据 - 修饰的 factorial 调用自身,而不是原始版本,因为名称 factorial 现在指的是修饰的版本。


最好使用 functools.wraps在装饰器中。这会将原始函数的各种属性复制到修饰后的版本。

例如,没有换行:

import time 
 
def clock(func): 
    def clocked(*args): 
        ''' Clocking decoration wrapper ''' 
        t0 = time.perf_counter() 
        result = func(*args)  
        elapsed = time.perf_counter() - t0 
        name = func.__name__ 
        arg_str = ', '.join(repr(arg) for arg in args) 
        print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
        return result 
    return clocked 
 
@clock 
def factorial(n): 
    ''' Recursive factorial ''' 
    return 1 if n < 2 else n * factorial(n-1) 
 
print(factorial.__name__, factorial.__doc__) 

输出

clocked  Clocking decoration wrapper  

使用换行:

import time 
from functools import wraps 
 
def clock(func): 
    @wraps(func) 
    def clocked(*args): 
        ''' Clocking decoration wrapper ''' 
        t0 = time.perf_counter() 
        result = func(*args)  
        elapsed = time.perf_counter() - t0 
        name = func.__name__ 
        arg_str = ', '.join(repr(arg) for arg in args) 
        print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
        return result 
    return clocked 
 
@clock 
def factorial(n): 
    ''' Recursive factorial ''' 
    return 1 if n < 2 else n * factorial(n-1) 
 
print(factorial.__name__, factorial.__doc__) 

输出

factorial  Recursive factorial  

这就是我们在未修饰的版本上执行 print(factorial.__name__, factorial.__doc__) 时得到的结果。


如果您不希望 clock 装饰的递归函数打印所有递归调用的时间信息,它会变得有点棘手。

最简单的方法是不使用装饰器语法,只是将 clock 作为普通函数调用,这样我们就可以为该函数的计时版本取一个新名称:

def factorial(n): 
    return 1 if n < 2 else n * factorial(n-1) 
 
clocked_factorial = clock(factorial) 
 
for n in range(7): 
    print('%d! = %d' % (n, clocked_factorial(n))) 

输出

[0.00000602s] factorial(0) -> 1 
0! = 1 
[0.00000302s] factorial(1) -> 1 
1! = 1 
[0.00000581s] factorial(2) -> 2 
2! = 2 
[0.00000539s] factorial(3) -> 6 
3! = 6 
[0.00000651s] factorial(4) -> 24 
4! = 24 
[0.00000742s] factorial(5) -> 120 
5! = 120 
[0.00000834s] factorial(6) -> 720 
6! = 720 

另一种方法是将递归函数包装在非递归函数中,并将装饰器应用于新函数。

def factorial(n): 
    return 1 if n < 2 else n * factorial(n-1) 
 
@clock 
def nr_factorial(n): 
    return factorial(n) 
 
for n in range(3, 7): 
    print('%d! = %d' % (n, nr_factorial(n))) 

输出

[0.00001018s] nr_factorial(3) -> 6 
3! = 6 
[0.00000799s] nr_factorial(4) -> 24 
4! = 24 
[0.00000801s] nr_factorial(5) -> 120 
5! = 120 
[0.00000916s] nr_factorial(6) -> 720 
6! = 720 

另一种方法是修 retrofit 饰器,使其跟踪它是在执行递归的顶层还是其中一个内部层,并且只打印顶层的时间信息。此版本使用 nonlocal 指令,因此它仅适用于 Python 3,不适用于 Python 2。

def rclock(func): 
    top = True 
    @wraps(func) 
    def clocked(*args): 
        nonlocal top 
        if top: 
            top = False 
            t0 = time.perf_counter() 
            result = func(*args)  
            elapsed = time.perf_counter() - t0 
            name = func.__name__ 
            arg_str = ', '.join(repr(arg) for arg in args) 
            print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
        else: 
            result = func(*args) 
            top = True 
        return result 
    return clocked 
 
@rclock 
def factorial(n): 
    return 1 if n < 2 else n * factorial(n-1) 
 
for n in range(3, 7): 
    print('%d! = %d' % (n, factorial(n)))  

输出

[0.00001253s] factorial(3) -> 6 
3! = 6 
[0.00001205s] factorial(4) -> 24 
4! = 24 
[0.00001227s] factorial(5) -> 120 
5! = 120 
[0.00001422s] factorial(6) -> 720 
6! = 720 

rclock 函数可用于非递归函数,但仅使用 clock 的原始版本效率更高一些。


functools 中的另一个方便的函数是 lru_cache,如果您正在使用递归函数,您应该了解它。 .这保留了最近计算结果的缓存,因此它们不需要重新计算。这可以极大地加速递归函数。有关详细信息,请参阅文档。

我们可以将lru_cache与clock或rclock结合使用。

@lru_cache(None) 
@clock 
def factorial(n): 
    return 1 if n < 2 else n * factorial(n-1) 
 
for n in range(3, 7): 
    print('%d! = %d' % (n, factorial(n))) 

输出

[0.00000306s] factorial(1) -> 1 
[0.00017850s] factorial(2) -> 2 
[0.00022049s] factorial(3) -> 6 
3! = 6 
[0.00000542s] factorial(4) -> 24 
4! = 24 
[0.00000417s] factorial(5) -> 120 
5! = 120 
[0.00000409s] factorial(6) -> 720 
6! = 720 

如您所见,即使我们使用普通的 clock 装饰器,也只会为 4、5 和 6 的阶乘打印一行计时信息,因为较小的阶乘是从缓存而不是重新计算。