简介
本节介绍装饰器的概念。这是一个高级主题,我们只是略作探讨。
日志记录示例
考虑一个函数。
def add(x, y):
return x + y
现在,考虑为该函数添加了一些日志记录后的情况。
def add(x, y):
print('Calling add')
return x + y
现在来看另一个同样添加了一些日志记录的函数。
def sub(x, y):
print('Calling sub')
return x - y
观察
观察结果:这有点重复。
编写存在大量代码重复的程序通常非常烦人。编写这些程序很繁琐,而且难以维护。特别是当你决定要更改其工作方式时(例如,可能是不同类型的日志记录)。
实现日志记录的代码
也许你可以创建一个函数,用于生成添加了日志记录功能的函数。也就是一个包装器。
def logged(func):
def wrapper(*args, **kwargs):
print('Calling', func.__name__)
return func(*args, **kwargs)
return wrapper
现在使用它。
def add(x, y):
return x + y
logged_add = logged(add)
当你调用由 logged 返回的函数时会发生什么?
logged_add(3, 4) ## 你会看到日志消息出现
这个示例展示了创建所谓的 包装器函数 的过程。
包装器是一个围绕另一个函数的函数,它带有一些额外的处理步骤,但在其他方面与原始函数的工作方式完全相同。
>>> logged_add(3, 4)
Calling add ## 额外的输出。由包装器添加
7
>>>
注意:logged() 函数创建包装器并将其作为结果返回。
装饰器
在 Python 中,为函数添加包装器是非常常见的操作。正因如此,Python 为此提供了一种特殊的语法。
def add(x, y):
return x + y
add = logged(add)
## 特殊语法
@logged
def add(x, y):
return x + y
这种特殊语法执行的步骤与上述代码完全相同。装饰器只是一种新的语法形式。它用于“装饰”函数。
说明
装饰器还有许多比这里介绍的更微妙的细节。例如,在类中使用它们。或者对一个函数使用多个装饰器。然而,前面的示例很好地说明了它们的使用场景是如何出现的。通常,这是为了应对在广泛的函数定义中出现的重复代码。装饰器可以将这些代码移到一个集中的定义中。
练习 7.10:用于计时的装饰器
如果你定义一个函数,它的名称和模块会存储在 __name__ 和 __module__ 属性中。例如:
>>> def add(x,y):
return x+y
>>> add.__name__
'add'
>>> add.__module__
'__main__'
>>>
在 timethis.py 文件中,编写一个装饰器函数 timethis(func),它会在函数周围添加一层额外的逻辑,用于打印函数执行所需的时间。为此,你需要在函数调用前后添加计时代码,如下所示:
start = time.time()
r = func(*args,**kwargs)
end = time.time()
print('%s.%s: %f' % (func.__module__, func.__name__, end-start))
以下是你的装饰器应该如何工作的示例:
>>> from timethis import timethis
>>> @timethis
def countdown(n):
while n > 0:
n -= 1
>>> countdown(10000000)
__main__.countdown : 0.076562
>>>
讨论:这个 @timethis 装饰器可以放在任何函数定义之前。因此,你可以将它用作性能调优的诊断工具。
总结
恭喜你!你已经完成了函数装饰器实验。你可以在 LabEx 中练习更多实验来提升你的技能。