Python上下文管理器和with块详解


Posted in Python onSeptember 09, 2017

上下文管理器和with块,具体内容如下

上下文管理器对象存在的目的是管理 with 语句,就像迭代器的存在是为了管理 for 语句一样。

with 语句的目的是简化 try/finally 模式。这种模式用于保证一段代码运行完毕后执行某项操作,即便那段代码由于异常、 return 语句或 sys.exit() 调用而中止,也会执行指定的操作。 finally 子句中的代码通常用于释放重要的资源,或者还原临时变更的状态。

==上下文管理器协议包含enter和exit两个方法==。 with 语句开始运行时,会在上下文管理器对象上调用enter方法。 with 语句运行结束后,会在上下文管理器对象上调用exit方法,以此扮演 finally 子句的角色。

==执行 with 后面的表达式得到的结果是上下文管理器对象,把值绑定到目标变量上(as 子句)是在上下文管理器对象上调用enter方法的结果==。with 语句的 as 子句是可选的。对 open 函数来说,必须加上 as子句,以便获取文件的引用。不过,有些上下文管理器会返回 None,因为没什么有用的对象能提供给用户。

with open('mirror.py') as fp:
  ...

自定义的上下文类:

class A:
  def __init__(self, name):
    self.name = name

  def __enter__(self):
    print('enter')
    return self.name

  def __exit__(self, exc_type, exc_val, exc_tb):
    print('gone')

with A('xiaozhe') as dt:
  print(dt)

contextlib模块

contextlib 模块中还有一些类和其他函数,使用范围更广。

closing:如果对象提供了 close() 方法,但没有实现enter/exit协议,那么可以使用这个函数构建上下文管理器。
suppress:构建临时忽略指定异常的上下文管理器。
@contextmanager:==这个装饰器把简单的生成器函数变成上下文管理器==,这样就不用创建类去实现管理器协议了。
ContextDecorator:这是个基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数
ExitStack:这个上下文管理器能进入多个上下文管理器。 with 块结束时, ExitStack 按照后进先出的顺序调用栈中各个上下文管理器的exit方法。

==使用最广泛的是 @contextmanager 装饰器,因此要格外留心。这个装饰器也有迷惑人的一面,因为它与迭代无关,却要使用 yield 语句==。

使用@contextmanager

@contextmanager 装饰器能减少创建上下文管理器的样板代码量,不用编写一个完整的类定义enter和exit方法,而只需实现有一个 yield 语句的生成器,生成想让enter方法返回的值。

在使用 @contextmanager 装饰的生成器中, yield 语句的作用是把函数的定义体分成两部分: ==yield 语句前面的所有代码在 with 块开始时(即解释器调用enter方法时)执行, yield 语句后面的代码在 with 块结束时(即调用exit方法时)执行==。

import contextlib

@contextlib.contextmanager
def test(name):
  print('start')
  yield name
  print('end')

with test('zhexiao123') as dt:
  print(dt)
  print('doing something')

实现原理

contextlib.contextmanager 装饰器会把函数包装成实现enter和exit方法的类。类的名称是 _GeneratorContextManager。

这个类的enter方法有如下作用:
1. 调用生成器函数,保存生成器对象(这里把它称为 gen)。
2. 调用 next(gen),执行到 yield 关键字所在的位置。
3. 返回 next(gen) 产出的值,以便把产出的值绑定到 with/as 语句中的目标变量上。

with 块终止时,exit方法会做以下几件事:

1. 检查有没有把异常传给 exc_type;如果有,调用 gen.throw(exception),在生成器函数定义体中包含 yield 关键字的那一行抛出异常。
2. 否则,调用 next(gen),继续执行生成器函数定义体中 yield 语句之后的代码。

异常处理

为了告诉解释器异常已经处理了,exit方法会返回 True,此时解释器会压制异常。如果exit方法没有显式返回一个值,那么解释器得到的是 None,然后向上冒泡异常。

使用 @contextmanager 装饰器时,默认的行为是相反的:装饰器提供的exit方法假定发给生成器的所有异常都得到处理了,因此应该压制异常。 如果不想让 @contextmanager 压制异常,必须在被装饰的函数中显式重新抛出异常。

上面的代码有个bug:如果在 with 块中抛出了异常, Python 解释器会将其捕获,然后在 test 函数的 yield 表达式里再次抛出。但是,那里没有处理错误的代码,因此 test 函数会中止。

使用 @contextmanager 装饰器时,要把 yield 语句放在 try/finally 语句中,因为我们永远不知道上下文管理器的用户会在 with 块中做什么。

import contextlib

@contextlib.contextmanager
def test(name):
  print('start')

  try:
    yield name
  except:
    raise ValueError('error')
  finally:
    print('end')

with test('zhexiao123') as dt:
  print(dt)
  print('doing something')

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python安装与使用redis的方法
Apr 19 Python
Python下载指定页面上图片的方法
May 12 Python
Python实现二维曲线拟合的方法
Dec 29 Python
Python Web框架之Django框架Model基础详解
Aug 16 Python
django admin 自定义替换change页面模板的方法
Aug 23 Python
python+selenium定时爬取丁香园的新型冠状病毒数据并制作出类似的地图(部署到云服务器)
Feb 09 Python
PyCharm刷新项目(文件)目录的实现
Feb 14 Python
Python使用re模块验证危险字符
May 21 Python
计算Python Numpy向量之间的欧氏距离实例
May 22 Python
通过Python扫描代码关键字并进行预警的实现方法
May 24 Python
keras多显卡训练方式
Jun 10 Python
Python字符串格式化常用手段及注意事项
Jun 17 Python
Python使用asyncio包处理并发详解
Sep 09 #Python
Python协程的用法和例子详解
Sep 09 #Python
python利用dir函数查看类中所有成员函数示例代码
Sep 08 #Python
Python使用回溯法子集树模板解决爬楼梯问题示例
Sep 08 #Python
Python使用回溯法子集树模板获取最长公共子序列(LCS)的方法
Sep 08 #Python
python中实现指定时间调用函数示例代码
Sep 08 #Python
Python基于回溯法子集树模板解决最佳作业调度问题示例
Sep 08 #Python
You might like
[FAQ]PHP中的一些常识:类篇
2006/10/09 PHP
完美解决令人抓狂的zend studio 7代码提示(content Assist)速度慢的问题
2013/06/20 PHP
PHP 文件编程综合案例-文件上传的实现
2013/07/03 PHP
PHP中4个加速、缓存扩展的区别和选用建议
2014/03/12 PHP
PHP连接Nginx服务器并解析Nginx日志的方法
2015/08/16 PHP
解决php-fpm.service not found问题的办法
2017/06/06 PHP
js取消单选按钮选中并判断对象是否为空
2013/11/14 Javascript
jQuery filter函数使用方法
2014/05/19 Javascript
jQuery中的read和JavaScript中的onload函数的区别
2014/08/27 Javascript
js表单中选择框值的获取及表单的序列化
2015/12/17 Javascript
JavaScript实现打开链接页面的方式汇总
2016/06/02 Javascript
微信小程序实现皮肤功能(夜间模式)
2017/06/18 Javascript
React中常见的动画实现的几种方式
2018/01/10 Javascript
微信小程序授权登录及解密unionId出错的方法
2018/09/26 Javascript
说说如何在Vue.js中实现数字输入组件的方法
2019/01/08 Javascript
vue+element tabs选项卡分页效果
2020/06/29 Javascript
Jquery+AJAX实现无刷新上传并重命名文件操作示例【PHP后台接收】
2020/05/29 jQuery
pycharm 取消默认的右击运行unittest的方法
2018/11/29 Python
python 检查文件mime类型的方法
2018/12/08 Python
对python实现二维函数高次拟合的示例详解
2018/12/29 Python
python 监听salt job状态,并任务数据推送到redis中的方法
2019/01/14 Python
Django中使用Json返回数据的实现方法
2020/06/03 Python
越南电子产品购物网站:FPT Shop
2017/12/02 全球购物
西班牙最好的在线购买葡萄酒的商店:Vinoseleccion
2019/10/30 全球购物
乌克兰品牌化妆品和香水在线商店:Bomond
2020/01/14 全球购物
市场安全管理制度
2014/01/26 职场文书
模具毕业生推荐信
2014/02/15 职场文书
大学生个人求职信例文
2014/07/07 职场文书
课外访万家心得体会
2014/09/03 职场文书
学校运动会广播稿范文
2014/10/02 职场文书
自主招生自荐信范文
2015/03/04 职场文书
2015年采购员工作总结
2015/04/27 职场文书
涨工资申请书应该怎么写?
2019/07/08 职场文书
浅谈:电影《孔子》观后感(范文)
2019/10/14 职场文书
浅谈Python响应式类库RxPy
2021/06/14 Python
python实现对doc、txt、xls等文档的读写操作
2022/04/02 Python