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在装饰器中使用是个好主意。这将原始函数的各种属性复制到装饰版本。
例如,没有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与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 的阶乘打印了一行时间信息,因为较小的阶乘是从缓存中读取而不是重新计算。
| 归档时间: | 
 | 
| 查看次数: | 1256 次 | 
| 最近记录: |