如何用上下文管理器扩展 Python 计时器

上文中我们创建的第一个 Python 计时器类,然后逐步扩展我们 Timer 类,其代码也是较为丰富强大。我们不能满足于此,仍然需要模板一些代码来使用Timer:

首先,实例化类其次,在要计时的代码块之前调用.start()最后,在代码块之后调用.stop()

如何用上下文管理器扩展 Python 计时器

一个 Python 定时器上下文管理器

Python 有一个独特的构造,用于在代码块之前和之后调用函数:上下文管理器。

了解 Python 中的上下文管理器

上下文管理器长期以来一直是 Python 中重要的一部分。由 PEP 343 于 2005 年引入,并首次在 Python 2.5 中实现。可以使用 with 关键字识别代码中的上下文管理器:

with EXPRESSION as VARIABLE:BLOCK

登录后复制

EXPRESSION 是一些返回上下文管理器的 Python 表达式。首先上下文管理器绑定到变量名 VARIABLE上,BLOCK 可以是任何常规的 Python 代码块。上下文管理器保证程序在 BLOCK 之前调用一些代码,在 BLOCK 执行之后调用一些其他代码。这样即使 BLOCK 引发异常,后者也是会照样执行。

上下文管理器最常见的用途是处理不同的资源,如文件、锁和数据库连接等。上下文管理器用于使用资源后释放和清理资源。以下示例仅通过打印包含冒号的行来演示 timer.py 的基本结构。此外,它展示了在 Python 中打开文件的常用习语:

with open("timer.py") as fp:print("".join(ln for ln in fp if ":" in ln))class TimerError(Exception):class Timer:timers: ClassVar[Dict[str, float]] = {}name: Optional[str] = Nonetext: str = "Elapsed time: {:0.4f} seconds"logger: Optional[Callable[[str], None]] = print_start_time: Optional[float] = field(default=None, init=False, repr=False)def __post_init__(self) -> None:if self.name is not None:def start(self) -> None:if self._start_time is not None:def stop(self) -> float:if self._start_time is None:if self.logger:if self.name:

登录后复制

注意,使用 open() 作为上下文管理器,文件指针fp 不会显式关闭,可以确认 fp 已自动关闭:

fp.closed

登录后复制

True

登录后复制

在此示例中,open(“timer.py”) 是一个返回上下文管理器的表达式。该上下文管理器绑定到名称 fp。上下文管理器在 print() 执行期间有效。这个单行代码块在 fp 的上下文中执行。

fp 是上下文管理器是什么意思? 从技术上讲,就是 fp 实现了 上下文管理器协议。Python 语言底层有许多不同的协议。可以将协议视为说明我们代码必须实现哪些特定方法的合同。

上下文管理器协议由两种方法组成:

进入与上下文管理器相关的上下文时调用.__enter__()。退出与上下文管理器相关的上下文时调用.__exit__()。

换句话说,要自己创建上下文管理器,需要编写一个实现 .__enter__() 和 .__exit__() 的类。试试 Hello, World!上下文管理器示例:

# studio.pyclass Studio:def __init__(self, name):self.name = namedef __enter__(self):print(f"你好 {self.name}")return selfdef __exit__(self, exc_type, exc_value, exc_tb):print(f"一会儿见, {self.name}")

登录后复制

Studio是一个上下文管理器,它实现了上下文管理器协议,使用如下:

from studio import Studiowith Studio("云朵君"):print("正在忙 ...")

登录后复制

你好 云朵君正在忙 ...一会儿见, 云朵君

登录后复制

首先,注意 .__enter__() 在做事之前是如何被调用的,而 .__exit__() 是在做事之后被调用的。该示例中,没有引用上下文管理器,因此不需要使用 as 为上下文管理器命名。

接下来,注意 self.__enter__() 的返回值受 as 约束。创建上下文管理器时,通常希望从 .__enter__() 返回 self 。可以按如下方式使用该返回值:

from greeter import Greeterwith Greeter("云朵君") as grt:print(f"{grt.name} 正在忙 ...")

登录后复制

你好 云朵君云朵君 正在忙 ...一会儿见, 云朵君

登录后复制

在写 __exit__ 函数时,需要注意的事,它必须要有这三个参数:

exc_type:异常类型exc_val:异常值exc_tb:异常的错误栈信息

这三个参数用于上下文管理器中的错误处理,它们以 sys.exc_info() 的返回值返回。当主逻辑代码没有报异常时,这三个参数将都为None。

如果在执行块时发生异常,那么代码将使用异常类型、异常实例和回溯对象(即exc_type、exc_value和exc_tb)调用 .__exit__() 。通常情况下,这些在上下文管理器中会被忽略,而在引发异常之前调用 .__exit__():

from greeter import Greeterwith Greeter("云朵君") as grt:print(f"{grt.age} does not exist")

登录后复制

你好 云朵君一会儿见, 云朵君Traceback (most recent call last):File "", line 2, in AttributeError: 'Greeter' object has no attribute 'age'

登录后复制

可以看到,即使代码中有错误,还是照样打印了 “一会儿见, 云朵君”。

理解并使用 contextlib

现在我们初步了解了上下文管理器是什么以及如何创建自己的上下文管理器。在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。

这个点Python早就想到了。它给我们提供了一个装饰器,你只要按照它的代码协议来实现函数内容,就可以将这个函数对象变成一个上下文管理器。

我们按照 contextlib 的协议来自己实现一个上下文管理器,为了更加直观我们换个用例,创建一个我们常用且熟悉的打开文件(with open)的上下文管理器。

import contextlib@contextlib.contextmanagerdef open_func(file_name):# __enter__方法print('open file:', file_name, 'in __enter__')file_handler = open(file_name, 'r') # 【重点】:yieldyield file_handler# __exit__方法print('close file:', file_name, 'in __exit__')file_handler.close()returnwith open_func('test.txt') as file_in:for line in file_in:print(line)

登录后复制

在被装饰函数里,必须是一个生成器(带有yield),而 yield 之前的代码,就相当于__enter__里的内容。yield 之后的代码,就相当于__exit__ 里的内容。

上面这段代码只能实现上下文管理器的第一个目的(管理资源),并不能实现第二个目的(处理异常)。

如果要处理异常,可以改成下面这个样子。

import contextlib@contextlib.contextmanagerdef open_func(file_name):# __enter__方法print('open file:', file_name, 'in __enter__')file_handler = open(file_name, 'r')try:yield file_handlerexcept Exception as exc:# deal with exceptionprint('the exception was thrown')finally:print('close file:', file_name, 'in __exit__')file_handler.close()returnwith open_func('test.txt') as file_in:for line in file_in:1/0print(line)

登录后复制

Python 标准库中的 contextlib包括定义新上下文管理器的便捷方法,以及可用于关闭对象、抑制错误甚至什么都不做的现成上下文管理器!

创建 Python 计时器上下文管理器

了解了上下文管理器的一般工作方式后,要想知道它们是如何帮助处理时序代码呢?假设如果可以在代码块之前和之后运行某些函数,那么就可以简化 Python 计时器的工作方式。其实,上下文管理器可以自动为计时时显式调用 .start() 和.stop()。

同样,要让 Timer 作为上下文管理器工作,它需要遵守上下文管理器协议,换句话说,它必须实现 .__enter__() 和 .__exit__() 方法来启动和停止 Python 计时器。从目前的代码中可以看出,所有必要的功能其实都已经可用,因此只需将以下方法添加到之前编写的的 Timer 类中即可:

# timer.py@dataclassclass Timer:# 其他代码保持不变def __enter__(self):"""Start a new timer as a context manager"""self.start()return selfdef __exit__(self, *exc_info):"""Stop the context manager timer"""self.stop()

登录后复制

Timer 现在就是一个上下文管理器。实现的重要部分是在进入上下文时, .__enter__() 调用 .start() 启动 Python 计时器,而在代码离开上下文时, .__exit__() 使用 .stop() 停止 Python 计时器。

from timer import Timerimport timewith Timer():time.sleep(0.7)

登录后复制

Elapsed time: 0.7012 seconds

登录后复制

此处注意两个更微妙的细节:

.__enter__()​ 返回self​,Timer 实例,它允许用户使用as​ 将Timer ​实例绑定到变量。例如,使用with Timer() as t:​ 将创建指向Timer ​对象的变量t。.__exit__()​ 需要三个参数,其中包含有关上下文执行期间发生的任何异常的信息。代码中,这些参数被打包到一个名为exc_info 的元组中,然后被忽略,此时 Timer 不会尝试任何异常处理。

在这种情况下不会处理任何异常。上下文管理器的一大特点是,无论上下文如何退出,都会确保调用.__exit__()。在以下示例中,创建除零公式模拟异常查看代码功能:

from timer import Timerwith Timer():for num in range(-3, 3):print(f"1 / {num} = {1 / num:.3f}")

登录后复制

1 / -3 = -0.3331 / -2 = -0.5001 / -1 = -1.000Elapsed time: 0.0001 secondsTraceback (most recent call last):File "", line 3, in ZeroDivisionError: division by zero

登录后复制

注意 ,即使代码抛出异常,Timer 也会打印出经过的时间。

使用 Python 定时器上下文管理器

现在我们将一起学习如何使用 Timer 上下文管理器来计时 “下载数据” 程序。回想一下之前是如何使用 Timer 的:

# download_data.pyimport requestsfrom timer import Timerdef main():t = Timer()t.start()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) t.stop()with open('dataset/datasets.zip', 'wb') as f:f.write(res.content)if __name__ == "__main__":main()

登录后复制

我们正在对 requests.get() 的调用进行记时监控。使用上下文管理器可以使代码更短、更简单、更易读:

# download_data.pyimport requestsfrom timer import Timerdef main():source_url = 'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=%2Fall_six_datasets.zip&dl=1'headers = {'User-Agent': 'Mozilla/5.0'}with Timer():res = requests.get(source_url, headers=headers)with open('dataset/datasets.zip', 'wb') as f:f.write(res.content)if __name__ == "__main__":main()

登录后复制

此代码实际上与上面的代码相同。主要区别在于没有定义无关变量t,在命名空间上无多余的东西。

写在最后

将上下文管理器功能添加到 Python 计时器类有几个优点:

省时省力:只需要一行额外的代码即可为代码块的执行计时。可读性高:调用上下文管理器是可读的,你可以更清楚地可视化你正在计时的代码块。

使用 Timer 作为上下文管理器几乎与直接使用 .start() 和 .stop() 一样灵活,同时它的样板代码更少。在该系列下一篇文章中,云朵君将和大家一起学习如何将 Timer 也用作装饰器,并用于代码中,从而更加容易地监控代码完整运行过程,我们一起期待吧!

以上就是如何用上下文管理器扩展 Python 计时器的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2241744.html

(0)
上一篇 2025年2月26日 20:43:38
下一篇 2025年2月20日 19:40:55

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 三种方案 | 抛弃for循环,让Python代码更pythonic !

    为什么要挑战自己在代码里不写 for loop?因为这样可以迫使你去学习使用比较高级、比较地道的语法或 library。文中以 python 为例子,讲了不少大家其实在别人的代码里都见过、但自己很少用的语法。 自从我开始探索 Python …

    2025年2月26日
    200
  • 提高 Python 代码可读性的五个基本技巧

    Python 中有许多方法可以帮助我们理解代码的内部工作原理,良好的编程习惯,可以使我们的工作事半功倍! 例如,我们最终可能会得到看起来很像下图中的代码。虽然不是最糟糕的,但是,我们需要扩展一些事情,例如: load_las_file 函数…

    2025年2月26日 编程技术
    200
  • 在 Kubernetes 上使用 Flask 搭建 Python 微服务

    微服务遵循领域驱动设计(DDD),与开发平台无关。Python 微服务也不例外。Python3 的面向对象特性使得按照 DDD 对服务进行建模变得更加容易。 微服务架构的强大之处在于它的多语言性。企业将其功能分解为一组微服务,每个团队自由选…

    2025年2月26日 编程技术
    200
  • TIOBE四月榜单:MATLAB 即将跌出前 20、Python 继续领跑

    毫无意外,Python 自去年 10 月以来,已连续 7 个月占据榜首。而主要应用于数值分析领域的 MATLAB 则是从上个月的第 15 名,暴跌至第 20 名,即将跌出前 20 的位置,这也是 MATLAB 在近 10 多年时间里的第一次…

    2025年2月26日
    200
  • 懒人神器 !一个创意十足的 Python 命令行工具

    当听到某些人说 xx 库非常好用的时候,我们总是忍不住想要去亲自试试。 有一些库,之所以好用,是对一些库做了更高级的封闭,你装了这个库,就会附带装了 n 多依赖库,就前一篇文章介绍的 streamlit 来说,依赖包就达 90 几个之多? …

    2025年2月26日 编程技术
    200
  • Python 作为小程序后端的三种方法

    你好,我是征哥。微信的小程序是一个很不错的体验,简单,上手快,这几天也在学习使用小程序,自己总结了三种用 Python 作为小程序后端的方式,供你参考。 方法一、微信的云托管[1]。 优点:不需要购买服务器,不需要域名备案,按使用量计费,D…

    2025年2月26日 编程技术
    200
  • 五个节约生命的Python小技巧

    Python是一种强大且易上手的语言,语法简洁优雅,不像Java那么繁琐废话,并且有一些特殊的函数或语法可以让代码变得更加简短精悍。 根据笔者经验,下面介绍常用的5个Python小技巧: 字符串操作列表推导lambda 及 map() 函数…

    2025年2月26日 编程技术
    200
  • 一文读懂 Python 装饰器

    Python 是一种对新手很友好的语言。但是,它也有很多较难掌握的高级功能,比如装饰器(decorator)。很多初学者一直不理解装饰器及其工作原理,在这篇文章中,我们将介绍装饰器的来龙去脉。 在 Python 中,函数是一种非常灵活的结构…

    2025年2月26日
    200
  • Python中的Deque: 实现高效的队列和堆栈

    Python 中的 deque 是一个低级别的、高度优化的双端队列,对于实现优雅、高效的Pythonic 队列和堆栈很有用,它们是计算中最常见的列表式数据类型。 本文中,云朵君将和大家一起学习如下: 开始使用deque有效地弹出和追加元素访…

    编程技术 2025年2月26日
    200
  • Python 比较两个日期的多种方法!

    人生苦短,快学Python! datetime 如果需要用Python处理日期和时间,大家肯定会先想到datetime、time、calendar等模块。在这其中,datetime模块主要是用来表示日期时间的,就是我们常说的年月日/时分秒。…

    2025年2月26日
    200

发表回复

登录后才能评论