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中函数的多种格式和使用实例及小技巧
Apr 13 Python
Python实现比较两个列表(list)范围
Jun 12 Python
将Emacs打造成强大的Python代码编辑工具
Nov 20 Python
Python实现压缩和解压缩ZIP文件的方法分析
Sep 28 Python
Python+OpenCV图片局部区域像素值处理详解
Jan 23 Python
python 并发编程 多路复用IO模型详解
Aug 20 Python
numpy np.newaxis 的实用分享
Nov 30 Python
基于TensorBoard中graph模块图结构分析
Feb 15 Python
Python+PyQt5实现灭霸响指功能
May 25 Python
python Cartopy的基础使用详解
Nov 01 Python
如何用PyPy让你的Python代码运行得更快
Dec 02 Python
基于Python 函数和方法的区别说明
Mar 24 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
谏山创故乡大分县日田市水坝将设立《进击的巨人》立艾伦、三笠以及阿尔敏的铜像!
2020/03/06 日漫
打造超酷的PHP数据饼图效果实现代码
2011/11/23 PHP
php的array数组和使用实例简明教程(容易理解)
2014/03/20 PHP
PHP与Java对比学习日期时间函数
2016/07/03 PHP
微信支付的开发流程详解
2016/09/13 PHP
PHP下 Mongodb 连接远程数据库的实例代码
2017/08/30 PHP
PHP设计模式之装饰器(装饰者)模式(Decorator)入门与应用详解
2019/12/13 PHP
javascript 伪数组实现方法
2010/10/11 Javascript
关于js中for in的缺陷浅析
2013/12/02 Javascript
多个jquery.datatable共存,checkbox全选异常的快速解决方法
2013/12/10 Javascript
jquery实现弹出div,始终显示在屏幕正中间的简单实例
2014/03/08 Javascript
IE浏览器中图片onload事件无效的解决方法
2014/04/29 Javascript
jquery转盘抽奖功能实现
2015/11/13 Javascript
JavaScript iframe数据共享接口实现方法
2016/01/06 Javascript
学习AngularJs:Directive指令用法(完整版)
2016/04/26 Javascript
create-react-app 修改为多入口编译的方法
2018/08/01 Javascript
vue实现图片上传到后台
2020/06/29 Javascript
Node.js web 应用如何封装到Docker容器中
2020/09/01 Javascript
Python ljust rjust center输出
2008/09/06 Python
Python中几种操作字符串的方法的介绍
2015/04/09 Python
深入源码解析Python中的对象与类型
2015/12/11 Python
python利用re,bs4,requests模块获取股票数据
2019/07/29 Python
Python列表原理与用法详解【创建、元素增加、删除、访问、计数、切片、遍历等】
2019/10/30 Python
Tensorflow 实现将图像与标签数据转化为tfRecord文件
2020/02/17 Python
解决echarts中饼图标签重叠的问题
2020/05/16 Python
Python 实现将numpy中的nan和inf,nan替换成对应的均值
2020/06/08 Python
Pandas的Apply函数具体使用
2020/07/21 Python
HTML5几个设计和修改的页面范例分享
2015/09/29 HTML / CSS
使用placeholder属性设置input文本框的提示信息
2020/02/19 HTML / CSS
英国在线汽车和面包车零件商店:Car Parts 4 Less
2018/08/15 全球购物
公司聘任书模板
2014/03/29 职场文书
法人委托书范本
2014/04/04 职场文书
社会治安综合治理管理责任书
2014/04/16 职场文书
计算机相关专业自荐信
2014/07/02 职场文书
民警群众路线教育实践活动对照检查材料
2014/10/04 职场文书
自主招生专家推荐信
2015/03/26 职场文书