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

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

作者: deniro | 来源:发表于2021-02-28 18:41 被阅读0次

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

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)
        logging.info('[%0.8fs] %s(%s) -> %r', elapsed, name, arg_str, result)
        return result

    return clocked

这里利用函数装饰器,在 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. 斐波那契函数。
import time
from course_6.clock_decorator import clock


@clock
def snooze(seconds):
    time.sleep(seconds)


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


if __name__ == '__main__':
    logging.info('snooze(.123) -> %s', snooze(.123))
    logging.info('factorial(6) -> %s', factorial(6))

运行结果:


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

@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)

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

如果输出 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. Python3 perf_counter() 用法.
  2. Python repr() 函数.
  3. Luciano Ramalho (作者),安道,吴珂 (译者).流畅的Python[M].人民邮电出版社,2017:319-322.

相关文章

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

    假设我们需要一个可以输出某个函数运行时长的装饰器。 1 基础实现 一种可能的定义方式为: 这里利用函数装饰器,在 ...

  • python 注解的应用(上)

    在 python 中是通过装饰模式来实现注解的,通过装饰模式来包裹被装饰的函数,在这个函数开始或结束运行装饰函数。...

  • Python装饰器高级用法

    转载至:Python装饰器高级用法 在 Python 中, 装饰器 一般用来修饰函数,实现公共功能,达到代码复用的...

  • 解析Python中的装饰器

    python中的函数也是对象,函数可以被当作变量传递。 装饰器在python中功能非常强大,装饰器允许对原有函数行...

  • 11.装饰器(注解)、深浅拷贝

    一、装饰器 1). 装饰器概述 装饰器:Python中的代理模式的实现。 对其他函数进行增强。 原则:不修改被修饰...

  • 《流畅的Python》读书笔记二

    函数装饰器在导入模块时立即执行,而被装饰的函数只有在明确调用时运行 为了理解 Python 中的赋值语句,应该始终...

  • Python-装饰器

    以装饰一个函数为例子, 打印出函数的开始执行时间。 常规方式实现: 输出: 装饰器方式实现: 输出:

  • python 装饰器的使用详解

    注意事项 何时执行装饰器函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。这突出了 Python ...

  • Python 装饰器的诞生过程

    Python中的装饰器是通过利用了函数特性的闭包实现的,所以在讲装饰器之前,我们需要先了解函数特性,以及闭包是怎么...

  • 只需四步,让你了解Python装饰器的诞生过程

    Python中的装饰器是通过利用了函数特性的闭包实现的,所以在讲装饰器之前,我们需要先了解函数特性,以及闭包是怎么...

网友评论

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

    本文链接:https://www.haomeiwen.com/subject/eidifltx.html