返回介绍

7.7 实现一个简单的装饰器

发布于 2024-02-05 21:59:47 字数 3494 浏览 0 评论 0 收藏 0

示例 7-15 定义了一个装饰器,它会在每次调用被装饰的函数时计时,然后把经过的时间、传入的参数和调用的结果打印出来。

示例 7-15 一个简单的装饰器,输出函数的运行时间

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 #  ➌

❶ 定义内部函数 clocked,它接受任意个定位参数。

❷ 这行代码可用,是因为 clocked 的闭包中包含自由变量 func。

❸ 返回内部函数,取代被装饰的函数。示例 7-16 演示了 clock 装饰器的用法。

示例 7-16 使用 clock 装饰器

# clockdeco_demo.py

import time
from clockdeco 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__':
  print('*' * 40, 'Calling snooze(.123)')
  snooze(.123)
  print('*' * 40, 'Calling factorial(6)')
  print('6! =', factorial(6))

运行示例 7-16 得到的输出如下:

$ python3 clockdeco_demo.py
**************************************** Calling snooze(.123)
[0.12405610s] snooze(.123) -> None
**************************************** Calling factorial(6)
[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
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)

因此,在两个示例中,factorial 会作为 func 参数传给 clock(参见示例 7-15)。然后, clock 函数会返回 clocked 函数,Python 解释器在背后会把 clocked 赋值给 factorial。其实,导入 clockdeco_demo 模块后查看 factorial 的 __name__ 属性,会得到如下结果:

>>> import clockdeco_demo
>>> clockdeco_demo.factorial.__name__
'clocked'
>>>

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

(1) 记录初始时间 t0。

(2) 调用原来的 factorial 函数,保存结果。

(3) 计算经过的时间。

(4) 格式化收集的数据,然后打印出来。

(5) 返回第 2 步保存的结果。

这是装饰器的典型行为:把被装饰的函数替换成新函数,二者接受相同的参数,而且(通常)返回被装饰的函数本该返回的值,同时还会做些额外操作。

 Gamma 等人写的《设计模式:可复用面向对象软件的基础》一书是这样概述“装饰器”模式的:“动态地给一个对象添加一些额外的职责。”函数装饰器符合这一说法。但是,在实现层面,Python 装饰器与《设计模式:可复用面向对象软件的基础》中所述的“装饰器”没有多少相似之处。“杂谈”会进一步探讨这个话题。

示例 7-15 中实现的 clock 装饰器有几个缺点:不支持关键字参数,而且遮盖了被装饰函数的 __name__ 和 __doc__ 属性。示例 7-17 使用 functools.wraps 装饰器把相关的属性从 func 复制到 clocked 中。此外,这个新版还能正确处理关键字参数。

示例 7-17 改进后的 clock 装饰器

# clockdeco2.py

import time
import functools

def clock(func):
  @functools.wraps(func)
  def clocked(*args, **kwargs):
    t0 = time.time()
    result = func(*args, **kwargs)
    elapsed = time.time() - t0
    name = func.__name__
    arg_lst = []
    if args:
      arg_lst.append(', '.join(repr(arg) for arg in args))
    if kwargs:
      pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
      arg_lst.append(', '.join(pairs))
    arg_str = ', '.join(arg_lst)
    print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
    return result
  return clocked

functools.wraps 只是标准库中拿来即用的装饰器之一。下一节将介绍 functools 模块中最让人印象深刻的两个装饰器:lru_cache 和 singledispatch。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文