Python 实现一个计时功能的装饰器

hochie
发布于 2020-10-4 10:44
浏览
0收藏

Python 实现一个计时功能的装饰器-鸿蒙开发者社区下面的装饰器clock会打印函数的运行时间

# descrbe.py
import time
import functools

def clock(func):
    """this is outer clock function"""

    @functools.wraps(func)  # --> 4
    def clocked(*args, **kwargs):  # -- 1
        """this is inner clocked function"""
        start_time = time.time()
        result = func(*args, **kwargs)  # --> 2
        time_cost = time.time() - start_time
        print(func.__name__ + " func time_cost -> {}".format(time_cost))
        return result
    return clocked  # --> 3

@functools.lru_cache()  # --> 5
@clock  # --> 6
def fib(n):
    """this is fibonacci function"""
    return n if n < 2 else fib(n - 1) + fib(n - 2)

if __name__ == "__main__":
    # 如果有 @functools.wraps(func)  # --> 4 大多数情况下我们希望的输出是这样的
    fib(1) # 输出 fib func time_cost -> 9.5367431640625e-07
    print(fib.__name__)  # 输出 fib
    print(fib.__doc__)  # 输出 this is fibonacci function
    
    # 如果没有@functools.wraps(func)  # --> 4
    fib(1) # 输出 fib func time_cost -> 9.5367431640625e-07
    print(fib.__name__)  # 输出 clocked
    print(fib.__doc__)  # 输出 this is inner clocked function

 

  • 定义了一个内部函数clocked,它接受任意定位参数以及关键字参数。
  • 这行代码可用,是因为clocked的闭包中包含了自由变量func。
  • 返回内部的函数,取代被装饰的函数。
  • functools.wraps是标准库中拿来即用的装饰器之一,它的作用是协助构建行为良好的装饰器。如果不加functools.wraps(func), 会遮盖被装饰函数的__name__和doc属性,

 

  • functools.lrucache()是非常实用的装饰器,它实现了备忘(memoization)功能。这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。LRU三个字母是“Least Recently Used”的缩写,表明缓存不会无限制增长,一段时间不用的缓存条目会被扔掉。这样就会显著提高程序的运行效率。lrucache可以使用两个可选的参数来配置。它的方法签名是lru_cache(maxsize=128, typed=False)。maxsize参数指定存储多少个调用的结果。缓存满了之后,旧的结果会被扔掉,腾出空间。为了得到最佳性能,maxsize应该设为2的幂。typed参数如果设为True,把不同参数类型得到的结果分开保存,即把通常认为相等的浮点数和整数参数(如 1 和 1.0)区分开。顺便说一下,因为lrucache使用字典存储结果,而且键根据调用时传入的定位参数和关键字参数创建,所以被lrucache装饰的函数,它的所有参数都必须是可散列的。

 

  • 实际上该处的工作原理如下:

 

@clock  # --> 6
def fib(n):
    """this is fibonacci function"""
    return n if n < 2 else fib(n - 1) + fib(n - 2)

 

等价于

 

fib = clock(fib) 

 

fib会作为func参数传给clock。然后,clock函数会返回clocked函数,Python解释器在背后会把 clocked赋值给fib。实际上如果我们没有添加@functools.wraps(func),我们在ipython中导入describe进一步观察:

 

In [1]: import describe                                                                                                                  
In [2]: describe.fib.__name__        
Out[2]: 'clocked'

 

所以,现在fib保存的是 clocked 函数的引用。自此之后,每次调用 fib(n),执行的都是 clocked(n)。clocked 大致做了下面几件事。

  • (1) 记录初始时间 t0。
  • (2) 调用原来的 fib 函数,保存结果。 
  • (3) 计算经过的时间。
  • (4) 打印出来格式化收集的数据
  • (5) 返回第 2 步保存的结果。
    这是装饰器的典型行为:把被装饰的函数替换成新函数,二者接受相同的参数,而且(通常)返回被装饰的函数本该返回的值,同时还会做些额外操作。

 

 

 

 

 

 

 

 

版权声明: 本文为 InfoQ 作者【王坤祥】的原创文章。

 

 

分类
已于2020-10-4 10:45:07修改
收藏
回复
举报
回复
    相关推荐