上文中,我们一起学习了手把手教你实现一个 Python 计时器。本文中,云朵君将和大家一起了解什么是上下文管理器 和 Python 的 with 语句,以及如何完成自定义。然后扩展 Timer 以便它也可以用作上下文管理器。最后,使用 Timer 作为上下文管理器如何简化我们自己的代码。

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

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

一个 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] = None
    text: 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.py
class Studio:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print(f"你好 {self.name}")
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        print(f"一会儿见, {self.name}")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

理解并使用 contextlib

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

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

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

import contextlib

@contextlib.contextmanager
def open_func(file_name):
    # __enter__方法
    print('open file:', file_name, 'in __enter__')
    file_handler = open(file_name, 'r')
 
    # 【重点】:yield
    yield file_handler

    # __exit__方法
    print('close file:', file_name, 'in __exit__')
    file_handler.close()
    return

with open_func('test.txt') as file_in:
    for line in file_in:
        print(line)

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

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

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

import contextlib

@contextlib.contextmanager
def open_func(file_name):
    # __enter__方法
    print('open file:', file_name, 'in __enter__')
    file_handler = open(file_name, 'r')

    try:
        yield file_handler
    except Exception as exc:
        # deal with exception
        print('the exception was thrown')
    finally:
        print('close file:', file_name, 'in __exit__')
        file_handler.close()
        return

with open_func('test.txt') as file_in:
    for line in file_in:
        1/0
        print(line)

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

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

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

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

# timer.py
@dataclass
class Timer:
    # 其他代码保持不变

    def __enter__(self):
        """Start a new timer as a context manager"""
        self.start()
        return self

    def __exit__(self, *exc_info):
        """Stop the context manager timer"""
        self.stop()

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

from timer import Timer
import time
with 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 Timer
with Timer():
    for num in range(-3, 3):
        print(f"1 / {num} = {1 / num:.3f}")

1 / -3 = -0.333
1 / -2 = -0.500
1 / -1 = -1.000
Elapsed time: 0.0001 seconds
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
ZeroDivisionError: division by zero

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

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

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

# download_data.py
import requests
from timer import Timer
def main():
    t = Timer()
    t.start()
    source_url = 'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=/all_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.py
import requests
from timer import Timer
def main():
    source_url = 'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=/all_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() 一样灵活,同时它的样板代码更少。

以上就是详解利用上下文管理器扩展Python计时器的详细内容,更多关于Python上下文管理器 计时器的资料请关注Devmax其它相关文章!

详解利用上下文管理器扩展Python计时器的更多相关文章

  1. XCode 3.2 Ruby和Python模板

    在xcode3.2下,我的ObjectiveCPython/Ruby项目仍然可以打开更新和编译,但是你无法创建新项目.鉴于xcode3.2中缺少ruby和python的所有痕迹(即创建项目并添加新的ruby/python文件),是否有一种简单的方法可以再次安装模板?我发现了一些关于将它们复制到某个文件夹的信息,但我似乎无法让它工作,我怀疑文件夹的位置已经改变为3.2.解决方法3.2中的应用程序模板

  2. xcode – 当NSMenu在Swift中打开时,NSTimer不会触发

    这不是什么大不了的事,但我不希望用户与会话混淆,如果他们看到计时器停机就会挂起.解决方法您只需将计时器添加到主runloop,如下所示:斯威夫特3Swift2.x

  3. xcode – Swift计时器,以毫秒为单位

    我想每毫秒更改一次计时器,但它不能按预期工作.结果是计时器在毫秒部分(00:100)中更改为100,然后更改为01:00=40实际秒邓肯方法:结果:456680125.54539第一次打印解决方法正如Martin在评论中所说,定时器的分辨率为50-100毫秒(0.05到0.1秒).尝试运行时间间隔短于此的计时器将无法提供可靠的结果.此外,计时器不是实时的.它们取决于它们所连接的运行循环,如果运行循

  4. 在iOS应用程序中使用dispatch_source_t无法在GCD块中运行计时器

    我想在GCD块中创建一个定时器来将其用作后台任务.但是,当我看到定时器火从来没有.这是我的代码:那么这是什么问题?解决方法您必须使您的dispatch_source_t成为一个类属性或实例变量,因此它不会超出范围.如果你这样做,你的代码可以正常工作,例如:另请注意,如果您希望在启动后台进程后更改BOOL的值,您可能也希望将其设置为类属性,如上所示.我也将其更名为观察消息,使其目的更为简单.

  5. 显示在视图控制器之间保留的iOS应用程序的计时器

    我一直试图通过使用NSTimer在我的应用程序的左下角显示一个计时器,并将“经过时间”显示为左下角的UILabel,但它并没有为我工作.计时器实际上工作,但我不能让它由按钮触发.我正在尝试让计时器继续运行,而不是在进入下一个storyboard/xib文件时重新启动.解决方法要在按下按钮时实现计时器操作,您需要在IBAction方法上编写它,如:要存储以前的值,可以使用NSUserDefaults或sqlite数据库.为此,我建议NSUserDefaults.更改aTime方法,如:

  6. ios – NSTimer不会因无效而停止

    我像这样添加计时器这是我班级的NSTimer属性.然后我点击按钮就停止它fbt它是我班级的实例.如果我只调用invalidate然后它不会停止,但如果我将它设置为nil然后我得到了EXC_BREAKPOINT这里选择器中的repeatTim方法的代码我试着调用init并使其无效它也不会停止计时器.解决方法阅读NSTimer的文档:Therearethreewaystocreateatimer:Us

  7. ios – 在发射开始后更改CAEmitterLayer的CAEmitterCell属性

    当我第一次设置发射器时,我可以这样做:但是说我创建了一个5秒后触发的计时器,我这样做:计时器触发,但CAEmitterCell的yacceleration不会更改.或者至少屏幕上的粒子发射没有任何变化.如何让CAEmitterCell尊重我对其属性所做的更改?解决方法这不是很明显,但这是解决方案:“cell”是这里给出的名字:

  8. ios – 如何在进入后台后杀死NSTimer并在应用程序恢复活动后创建新的计时器?

    或许我应该把所有与计时器相关的代码放在AppDelegate.swift中?

  9. ios – 如何维护两个不同设备之间的时钟会话?

    我正在研究iOS应用程序,它需要在接受两个设备用户时在两个设备之间维持时钟计时器会话?但我不确定如何在两台设备上都没有时间缺陷的情况下实现这一目标?

  10. ios – 如何在Swift中实现非常精确的计时?

    我正在开发一个具有琶音/排序功能的音乐应用程序,需要很高的计时准确性.目前,使用“Timer”我已经达到了平均抖动约为5ms的精度,但最大抖动约为11ms,这对于8号,16号音符的快速琶音来说是不可接受的.特别是第32条.我已经读过’CAdisplayLink’比’Timer’更准确,但由于它的准确度限制在1/60秒,看起来它的准确性不如我用Timer实现了.潜入CoreAudio是实现我想要的唯一途径吗?还有其他方法可以实现更精确的计时吗?

随机推荐

  1. 10 个Python中Pip的使用技巧分享

    众所周知,pip 可以安装、更新、卸载 Python 的第三方库,非常方便。本文小编为大家总结了Python中Pip的使用技巧,需要的可以参考一下

  2. python数学建模之三大模型与十大常用算法详情

    这篇文章主要介绍了python数学建模之三大模型与十大常用算法详情,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感想取得小伙伴可以参考一下

  3. Python爬取奶茶店数据分析哪家最好喝以及性价比

    这篇文章主要介绍了用Python告诉你奶茶哪家最好喝性价比最高,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧

  4. 使用pyinstaller打包.exe文件的详细教程

    PyInstaller是一个跨平台的Python应用打包工具,能够把 Python 脚本及其所在的 Python 解释器打包成可执行文件,下面这篇文章主要给大家介绍了关于使用pyinstaller打包.exe文件的相关资料,需要的朋友可以参考下

  5. 基于Python实现射击小游戏的制作

    这篇文章主要介绍了如何利用Python制作一个自己专属的第一人称射击小游戏,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起动手试一试

  6. Python list append方法之给列表追加元素

    这篇文章主要介绍了Python list append方法如何给列表追加元素,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  7. Pytest+Request+Allure+Jenkins实现接口自动化

    这篇文章介绍了Pytest+Request+Allure+Jenkins实现接口自动化的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  8. 利用python实现简单的情感分析实例教程

    商品评论挖掘、电影推荐、股市预测……情感分析大有用武之地,下面这篇文章主要给大家介绍了关于利用python实现简单的情感分析的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下

  9. 利用Python上传日志并监控告警的方法详解

    这篇文章将详细为大家介绍如何通过阿里云日志服务搭建一套通过Python上传日志、配置日志告警的监控服务,感兴趣的小伙伴可以了解一下

  10. Pycharm中运行程序在Python console中执行,不是直接Run问题

    这篇文章主要介绍了Pycharm中运行程序在Python console中执行,不是直接Run问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

返回
顶部