2017-04-16 64 views
1
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 

这个装饰时的说法是递归函数是如何工作的?为什么装饰器可以被执行很多次。怎么运行的?

+2

“iterable”的意思是“递归”吗? –

+0

对不起,应该是递归的 –

+0

只是对术语的评论,'clock'是一个装饰器,只被调用一次。 'factorial'由'clock'装饰器用'clocked'包装器装饰。所以这是每次被调用的'clocked'包装器。它被多次调用的原因是因为Python有后期绑定,所以对'factorial()'的递归调用在执行前并没有绑定到一个函数上,到那个时候,'factorial = clock(factorial)',即一个装饰'factorial'的形式。 – AChampion

回答

3

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


在修饰器中使用functools.wraps是一个好主意。这将原始函数的各种属性复制到装饰版本。

例如,在不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 

随着wraps

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 -decorated递归函数打印定时信息的所有递归调用,它变得有点棘手。

最简单的方法是不使用的修饰语法,只是叫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 

另一种方法是修改装饰,使其保持它无论是在执行递归或内部级别的一个顶层的轨道,只有打印顶层的时间信息。这个版本使用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结合clockrclock

@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,因为较小的阶乘从缓存中读取而不是被重新计算。

+0

是否表示装饰函数是递归的,并且会多次调用?并每次打印信息? –

+0

@DeanWang我刚刚给我的答案添加了更多信息。希望对你有帮助。 –

+0

非常感谢您的回答 –

1

当您将一个装饰器应用于一个函数时,该函数作为参数传递给装饰器。函数是递归还是不是无关紧要。

代码

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

相当于

def factorial(n): 
    return 1 if n < 2 else n*factorial(n-1) 
factorial = clock(factorial) 
+0

我很困惑为什么函数可以多次作为参数传递给装饰器。 –

+2

@DeanWang函数每次调用函数时只传递给装饰器一次 –

+0

,装饰器会被执行,对不对?递归被调用了很多次,装饰器被执行了很多次,对吧? –

0

装饰函数传递给装饰作为参数并返回另一个函数来代替原来的,返回的功能是没有递归函数,但是当你调用它时,它会调用原始的递归函数:

def clock(func): 
    def clocked(*args): 
    t0 = time.perf_counter() 
    result = func(*args) # You call your original recursive function here 
    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 

当你打电话给你的装饰功能factorial,你居然叫是clocked,它实际上调用下面一行factorial

result = func(*args) 

装饰器只执行一次。

的理解,你可以把你的功能变得@clock后下列之一:

def factorial(*args): 
    def _factorial(n): 
     return 1 if n < 2 else n*_factorial(n-1) 
    t0 = time.perf_counter() 
    result = _factorial(*args) 
    elapsed = time.perf_counter() - t0 
    name = _factorial.__name__ 
    arg_str = ', '.join(repr(arg) for arg in args) 
    print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result)) 
    return result 
+0

表示装饰函数是递归的,它会被多次调用?并每次打印信息? –

+0

装饰后的功能会多次调用,就像它没有装饰并正常调用一样。打印信息的逻辑在你的包装函数'clock'中,它只会被调用一次。 – shizhz

+0

@DeanWang,我更新了我的答案,以便于理解,查看出 – shizhz

0

也许它可以帮助承担的视图中的“语法糖”点。 这是从PEP 318具有修饰(I简化的例子)

当前语法功能装饰如在Python 2.4a2实现是:

@dec 
def func(arg1, arg2, ...): 
    pass 

这相当于:

def func(arg1, arg2, ...): 
    pass 
func = dec(func) 

正如你所看到的,装饰器函数只被调用一次,它返回的包装器被分配给装饰函数的名称。因此,无论何时通过其名称调用原始函数(例如在递归中),都会调用包装器(而不是装饰器函数)。