摘要:一个典型的上下文管理器类如下处理异常正如方法名明确告诉我们的,方法负责进入上下的准备工作,如果有需要可以返回一个值,这个值将会被赋值给中的。总结都是关于上下文管理器的内容,与协程关系不大。
Part 1 传送门
David Beazley 的博客
PPT 下载地址
在 Part 1 我们已经介绍了生成器的定义和生成器的操作,现在让我们开始使用生成器。Part 2 主要描述了如何使用 yield 和 contextmanager 创建一个上下文管理器,并解释了原理。
理解上下文可以联想我们做阅读理解时要解读文章某处的意思需要阅读该处前后段落,正是前后文提供了理解的“背景”。而程序的运行的上下文也可以理解为程序在运行时的某些变量,正是这些变量构成了运行环境,让程序可以完成操作。
Python 中的上下文管理器提供这样一种功能,为你的程序运行时提供一个特定“空间”,当进入这个空间时 Python 上下文管理器 为你做一些准备工作。这个“空间”中一般含有特殊的变量,你在这个“空间”中进行一些操作,然后离开。在你离开时 Python 上下文管理器又会帮你做一些收尾工作,保证不会污染运行环境。
下面是一些常见的代码模式
# 读取文件 f = open() # do something f.close() # 使用锁 lock.acquire() # do somethin lock.release() # 进行数据库操作 db.start_transaction() # do something db.commit() # 对某段代码进行计时 start = time.time() # do something end = time.time()
这些代码进行的都是“先做这个(准备工作,比如获取一个数据库连接),然后做这个(比如写入数据),最后整理工作环境(如提交改动,关闭链接,释放资源等)。
如果使用 with 可以这样写:
witn open(filename) as f: # do something pass with lock(): # do something pass
with 语句实际上使用了实现了 __enter__ 和 __exit__ 方法的上下文管理器类。一个典型的上下文管理器类如下:
clss ContextManager: def __enter__(self): return value def __exit__(self, exc_type, val, tb): if exec_type is None: return else: # 处理异常 return True if handled else False
正如方法名明确告诉我们的,__enter__ 方法负责进入上下的准备工作,如果有需要可以返回一个值,这个值将会被赋值给 with ContextManager() as ret_value 中的 ret_value 。__exit__ 则负责收尾工作,这包括了异常处理。
对于这样一段代码
with ContextManager() as var: # do something
相当于
ctxmanager = ContextManager() var = ctxmanager.__enter__() # do somethin ctxmanager.__exit__()
一个可用的例子:
import tempfile import shutil class TmpDir: def __enter__(self): self.dirname = tempfile.mkdtemp() return self.dirname def __exit__(self, exc, val, tb): shutil.rmtree(self.dirname)
这个上下文管理提供临时文件的功能,在 with 语句结束后会自动删除临时文件夹。
with TempDir() as dirname: # 使用临时文件夹进行一些操作 pass
关于上面两个特殊方法的文档可以在 Python 文档的 Context Manager Types 找到。另外关于 with 关键字的详细说明参考 PEP 343,不过这篇 PEP 不是很好读,Good Luck :simple_smile:!
使用 yield 和 contextmanager能看到这里的都应该对上下文管理器有所了解,准备好把 yield 加入我们的上下文管理器代码中。
先看一个例子
import tempfile, shutil from contextlib import contextmanager @contextmanager def tempdir(): outdir = tempfile.mkdtemp() try: yield outdir finally: shutil.rmtree(outdir)
与使用上下文管理器类的实现方式不同,这里我们没有显式实现 __enter__ 和 __exit__,而是通过 contextmanager 装饰器和 yield 实现,你可以试试这两种方式是等价的。
要理解上面的代码,可以把 yield 想象为一把剪刀,把这个函数一分为二,上部分相当于 __enter__,下部分相当于 __exit__。我这样说大家应该明白了吧。
import tempfile, shutil from contextlib import contextmanager @contextmanager def tempdir(): outdir = tempfile.mkdtemp() # try: # __enter__ yield outdir # --cut---╳----------------------------------- finally: # shutil.rmtree(outdir) # __exit__
实现“剪刀”功能关键在于 contextmanager 。对于上面的代码,我们来一步一步地结构它:
contextmanager 装饰器contextmanager 其实使用了一个上下文管理器类,这个类在在初始化时需要提供一个生成器。
class GeneratorCM: def __init__(self, gen): self.gen = gen def __enter__(self): ... def __exit__(self, exc, val, tb): ...
contextmanager 的实现如下
def contextmanager(func): def run(*args, **kwargs): return GeneratorCM(func(*args, **kwargs)) return run
由于 contextmanger 所装饰的函数里有 yield 所以我们在调用 func(*args, **kwargs) 时返回的是一个生成器。要使这个生成器前进,我们需要调用 next 函数
让生成器前进def __enter__(self): return next(self.gen)
GeneratorCM 的 __ente__ 方法会让生成器前进到 yield 语句处,并返回产出值。
收尾def __exit__(self, exc, val, tb): try: if exc is None: next(self.gen) else: self.gen.throw(exc, val, tb) raise RuntimeError("Generator didn"t stop") except StopIteration: return True except: if sys.exc_info()[1] is not val: raise
__exit__ 函数的逻辑比较复杂,如果没有传入异常,首先它会尝试对生成器调用 next,正常情况下这会抛出 StopIteration ,这个异常会被不做并返回 True ,告诉解释器正常退出;如果传入异常,会使用 throw 在 yield 处抛出这个异常;如果有其他未捕捉的错误,就重新抛出该错误。
实际的代码实现会更加复杂,还有一些异常情况没有处理
没有相关值的异常
在 with 语句块中抛出的 StopIteration
在上下文管理器中抛出的异常
如果你对怎么实现感兴趣,你可以阅读代码或者再一次阅读 PEP 343。
总结Part 2 都是关于上下文管理器的内容,与协程关系不大。但通过这部分我们可以看到 yield 完全不同的用法,也熟悉了控制流 (control-flow) ,这与 Part 3 的异步处理流程有很大关系。让我们 Part 3 再见。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/41947.html
摘要:生成器用于定义生成器函数只要存在该函数必定是一个生成器调用该函数返回一个生成器让一个生成器前进使用使一个生成器前进到下一个语句处,并将产出值作为其返回值。 前言 这篇文章大部分来自 David Beazley 在 PyCon 2014 的 PPT 《Generators: The Final Frontier》。这个PPT很长而且非常烧脑,建议在阅读前应了解 Python 的生成器与携...
摘要:源码之分析的协程原理分析版本为支持异步,实现了一个协程库。提供了回调函数注册当异步事件完成后,调用注册的回调中间结果保存结束结果返回等功能注册回调函数,当被解决时,改回调函数被调用。相当于唤醒已经处于状态的父协程,通过回调函数,再执行。 tornado 源码之 coroutine 分析 tornado 的协程原理分析 版本:4.3.0 为支持异步,tornado 实现了一个协程库。 ...
摘要:项目地址我之前翻译了协程原理这篇文章之后尝试用了模式下的协程进行异步开发,确实感受到协程所带来的好处至少是语法上的。 项目地址:https://git.io/pytips 我之前翻译了Python 3.5 协程原理这篇文章之后尝试用了 Tornado + Motor 模式下的协程进行异步开发,确实感受到协程所带来的好处(至少是语法上的:D)。至于协程的 async/await 语法是如...
摘要:协程的判断条件下面我们来着重看下的源码,因为从这里开始就涉及到协程的判断。第二点是关键点,用来判断该方法的调用是否使用到了协程。原理我们先来看下使用协程是怎么写的这是一个标准的协程写法,然后我们再套用上面的条件,发现完全匹配不到。 第一眼看,跟我之前印象中的有点区别(也不知道是什么版本),return的时候居然...
阅读 2393·2021-11-11 16:54
阅读 1203·2021-09-22 15:23
阅读 3642·2021-09-07 09:59
阅读 1988·2021-09-02 15:41
阅读 3282·2021-08-17 10:13
阅读 3036·2019-08-30 15:53
阅读 1233·2019-08-30 13:57
阅读 1208·2019-08-29 15:16