分享

说说在 Python 中如何实现输出指定函数运行时长的装饰器

 天穹的雄鹰 2021-03-20

假设我们需要一个可以输出某个函数运行时长的装饰器。

1 基础实现

一种可能的定义方式为:

这里利用函数装饰器,在 clock(func) 函数内部定义了一个 clock(*args) 函数,定义好后直接返回。内部利用 perf_count() 函数实现计算函数运行时长。每调用一次 perf_counter(),Python 就会记录一个时间点,类似于在秒表上按下开始计时键;当第二次调用该函数时,会计算与第一个时间点的时间长,类似于在秒表上按下结束计时键1

func.__name__ 会返回入参函数的名称。repr(arg) 会返回一个 arg的 string 格式2。通过一系列转换,我们就可以得到一个以逗号作为分隔符的入参字符串。

内部函数最后以这样的一种格式 [时长] 运行函数名(多个入参字符串) -> 输出结果 输出函数运行报告。其中的 %0.8fs 表示小数保留8位,然后再转换为字符串。

而外部函数最后返回这个 clocked(*args) 函数。

接着我们使用这个 clock 装饰器,来输出以下两个函数的运行报告:

  1. 睡眠函数;

  2. 斐波那契函数。


运行结果:


这里使用 @装饰函数名 这样的语法来包装我们需要运行的函数。

实际上等价于:


所以从写法上来讲,第一种方式更加简洁。

如果输出 logging.info('factorial.__name__ -> %s',factorial.__name__) 就会得到 factorial.__name__ -> clocked。这就说明了 factorial 实际上是 clocked 函数,也就是说factorial 函数已经被装饰为 clocked 函数。所以每次调用 factorial 函数,本质上就是调用 clocked 函数。

2 优化

前面说了,如果输出 logging.info('factorial.__name__ -> %s',factorial.__name__) 就会得到 factorial.__name__ -> clocked。也就是说,装饰函数 clock(func) 把 factorial(n) 函数给遮住了。如果我们不想被装饰函数的 __name__ 属性被遮住,可以这样做:

@functools.wraps 也是一个装饰器,它可以把 func 中的相关属性复制到 clocked 中。这样再次输出logging.info('factorial.__name__ -> %s',factorial.__name__) 就会得到 factorial.__name__ -> factorial 咯。


这实际上就是经典的装饰器设计模式,但在是实现方式上与普通的面向对象语言差别较大。普通的面向对象语言采用的是面向对象的编程方式,而 Python 采用的是面向函数的编程方式。

  1. Luciano Ramalho (作者),安道,吴珂 (译者).流畅的Python[M].人民邮电出版社,2017:319-322.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多