您现在的位置是:网站首页> 编程资料编程资料
详解利用装饰器扩展Python计时器_python_
2023-05-26
448人已围观
简介 详解利用装饰器扩展Python计时器_python_
介绍
在本文中,云朵君将和大家一起了解装饰器的工作原理,如何将我们之前定义的定时器类 Timer 扩展为装饰器,以及如何简化计时功能。最后对 Python 定时器系列文章做个小结。
这是我们手把手教你实现 Python 定时器的第三篇文章。前两篇:分别是手把手教你实现一个 Python 计时器,和用上下文管理器扩展 Python 计时器,使得我们的 Timer 类方便用、美观实用。
但我们并不满足于此,仍然有一个用例可以进一步简化它。假设我们需要跟踪代码库中一个给定函数所花费的时间。使用上下文管理器,基本上有两种不同的选择:
1. 每次调用函数时使用 Timer:
with Timer("some_name"): do_something() 当我们在一个py文件里多次调用函数 do_something(),那么这将会变得非常繁琐并且难以维护。
2. 将代码包装在上下文管理器中的函数中:
def do_something(): with Timer("some_name"): ... Timer 只需要在一个地方添加,但这会为do_something()的整个定义增加一个缩进级别。
更好的解决方案是使用 Timer 作为装饰器。装饰器是用于修改函数和类行为的强大构造。
理解 Python 中的装饰器
装饰器是包装另一个函数以修改其行为的函数。你可能会有疑问,这怎么实现呢?其实函数是 Python 中的first-class 对象,换句话说,函数可以以变量的形式传递给其他函数的参数,就像任何其他常规对象一样。因此此处有较大的灵活性,也是 Python 几个最强大功能的基础。
我们首先创建第一个示例,一个什么都不做的装饰器:
def turn_off(func): return lambda *args, **kwargs: None
首先注意这个turn_off()只是一个常规函数。之所以成为装饰器,是因为它将一个函数作为其唯一参数并返回另一个函数。我们可以使用turn_off()来修改其他函数,例如:
>>> print("Hello") Hello >>> print = turn_off(print) >>> print("Hush") >>> # Nothing is printed 代码行 print = turn_off(print) 用 turn_off() 装饰器装饰了 print 语句。实际上,它将函数 print() 替换为匿名函数 lambda *args, **kwargs: None 并返回 turn_off()。匿名函数 lambda 除了返回 None 之外什么都不做。
要定义更多丰富的装饰器,需要了解内部函数。内部函数是在另一个函数内部定义的函数,它的一种常见用途是创建函数工厂:
def create_multiplier(factor): def multiplier(num): return factor * num return multiplier
multiplier() 是一个内部函数,在 create_multiplier() 内部定义。注意可以访问 multiplier() 内部的因子,而 multiplier()未在 create_multiplier() 外部定义:
multiplier
Traceback (most recent call last):
File "", line 1, in
NameError: name 'multiplier' is not defined
相反,可以使用create_multiplier()创建新的 multiplier 函数,每个函数都基于不同的参数factor:
double = create_multiplier(factor=2) double(3)
6
quadruple = create_multiplier(factor=4) quadruple(7)
28
同样,可以使用内部函数来创建装饰器。装饰器是一个返回函数的函数:
def triple(func): def wrapper_triple(*args, **kwargs): print(f"Tripled {func.__name__!r}") value = func(*args, **kwargs) return value * 3 return wrapper_triple triple() 是一个装饰器,因为它是一个期望函数 func() 作为其唯一参数并返回另一个函数 wrapper_triple() 的函数。注意 triple() 本身的结构:
- 第 1 行开始了
triple()的定义,并期望一个函数作为参数。 - 第 2 到 5 行定义了内部函数
wrapper_triple()。 - 第 6 行返回
wrapper_triple()。
这是种定义装饰器的一般模式(注意内部函数的部分):
- 第 2 行开始
wrapper_triple()的定义。此函数将替换triple()修饰的任何函数。参数是*args和**kwargs,用于收集传递给函数的任何位置参数和关键字参数。我们可以灵活地在任何函数上使用triple()。 - 第 3 行打印出修饰函数的名称,并指出已对其应用了
triple()。 - 第 4 行调用
func(),triple()修饰的函数。它传递传递给wrapper_triple()的所有参数。 - 第 5 行将
func()的返回值增加三倍并将其返回。
接下来的代码中,knock() 是一个返回单词 Penny 的函数,将其传给triple() 函数,并看看输出结果是什么。
>>> def knock(): ... return "Penny! " >>> knock = triple(knock) >>> result = knock() Tripled 'knock' >>> result 'Penny! Penny! Penny! '
我们都知道,文本字符串与数字相乘,是字符串的一种重复形式,因此字符串 'Penny' 重复了 3 次。可以认为,装饰发生在knock = triple(knock)。
上述方法虽然实现了装饰器的功能,但似乎有点笨拙。PEP 318 引入了一种更方便的语法来应用装饰器。下面的 knock() 定义与上面的定义相同,但装饰器用法不同。
>>> @triple ... def knock(): ... return "Penny! " ... >>> result = knock() Tripled 'knock' >>> result 'Penny! Penny! Penny! '
@ 符号用于应用装饰器,@triple 表示 triple() 应用于紧随其后定义的函数。
Python 标准库中定义的装饰器方法之一是:@functools.wraps。这在定义你自己的装饰器时非常有用。前面说过,装饰器是用另一个函数替换了一个函数,会给你的函数带来一个微妙的变化:
knock
.wrapper_triple at 0x7fa3bfe5dd90>
@triple 装饰了 knock(),然后被 wrapper_triple() 内部函数替换,被装饰的函数的名字会变成装饰器函数,除了名称,还有文档字符串和其他元数据都将会被替换。但有时,我们并不总是想将被修饰的函数的所有信息都被修改了。此时 @functools.wraps 正好解决了这个问题,如下所示:
import functools def triple(func): @functools.wraps(func) def wrapper_triple(*args, **kwargs): print(f"Tripled {func.__name__!r}") value = func(*args, **kwargs) return value * 3 return wrapper_triple 使用 @triple 的这个新定义保留元数据:
@triple def knock(): return "Penny! " knock
注意knock() 即使在被装饰之后,也同样保留了它的原有函数名称。当定义装饰器时,使用 @functools.wraps 是一种不错的选择,可以为大多数装饰器使用的如下模板:
import functools def decorator(func): @functools.wraps(func) def wrapper_decorator(*args, **kwargs): # Do something before value = func(*args, **kwargs) # Do something after return value return wrapper_decorator
创建 Python 定时器装饰器
在本节中,云朵君将和大家一起学习如何扩展 Python 计时器,并以装饰器的形式使用它。接下来我们从头开始创建 Python 计时器装饰器。
根据上面的模板,我们只需要决定在调用装饰函数之前和之后要做什么。这与进入和退出上下文管理器时的注意事项类似。在调用修饰函数之前启动 Python 计时器,并在调用完成后停止 Python 计时器。可以按如下方式定义 @timer 装饰器:
import functools import time def timer(func): @functools.wraps(func) def wrapper_timer(*args, **kwargs): tic = time.perf_counter() value = func(*args, **kwargs) toc = time.perf_counter() elapsed_time = toc - tic print(f"Elapsed time: {elapsed_time:0.4f} seconds") return value return wrapper_timer 可以按如下方式应用 @timer:
@timer def download_data(): source_url = 'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=%2Fall_six_datasets.zip&dl=1' headers = {'User-Agent': 'Mozilla/5.0'} res = requests.get(source_url, headers=headers) download_data() # Python Timer Functions: Three Ways to Monitor Your Code [ ... ]
Elapsed time: 0.5414 second
回想一下,还可以将装饰器应用于先前定义的下载数据的函数:
requests.get = requests.get(source_url, headers=headers)
使用装饰器的一个优点是只需要应用一次,并且每次都会对函数计时:
data = requests.get(0)
Elapsed time: 0.5512 seconds
虽然@timer 顺利完成了对目标函数的定时。但从某种意义上说,你又回到了原点,因为该装饰器 @timer 失去了前面定义的类 Timer 的灵活性或便利性。换句话说,我们需要将 Timer 类表现得像一个装饰器。
现在我们似乎已经将装饰器用作应用于其他函数的函数,但其实不然,因为装饰器必须是可调用的。Python中有许多可调用的类型,可以通过在其类中定义特殊的.__call__()方法来使自己的对象可调用。以下函数和类的行为类似:
def square(num): return num ** 2 square(4)
16
class Squarer: def __call__(self, num): return num ** 2 square = Squarer() square(4)
16
这里,square 是一个可调用的实例,可以对数字求平方,就像square()第一个示例中的函数一样。
我们现在向现有Timer类添加装饰器功能,首先需要 import functools。
# timer.py import functools # ... @dataclass class Timer: # The rest of the code is unchanged def __call__(self, func): """Support using Timer as a decorator""" @functools.wraps(func) def wrapper_timer(*args, **kwargs): with self: return func(*args, **kwargs) return wrapper_timer
在之前定义的上下文管理器 Timer ,给我们带来了不少便利。而这里使用的装饰器,似乎更加方便。
@Timer(text="Downloaded the tutorial in {:.2f} seconds") def download_data(): source_url = 'ht
相关内容
- python可以美化表格数据输出结果的两个工具_python_
- python使用redis模块来跟redis实现交互_python_
- 如何使用Python Matplotlib绘制条形图_python_
- Python基础异常处理梳理总结_python_
- Python matplotlib.pyplot.hist()绘制直方图的方法实例_python_
- 如何解决pycharm中用matplotlib画图不显示中文的问题_python_
- python作图基础之plt.contour实例详解_python_
- Pytest+Request+Allure+Jenkins实现接口自动化_python_
- Python3.9用pip安装wordcloud库失败的解决过程_python_
- pytest多线程与多设备并发appium_python_
点击排行
本栏推荐
