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的Flask框架中Flask-Admin库的简单入门指引
Apr 07 Python
使用python爬虫实现网络股票信息爬取的demo
Jan 05 Python
彻底搞懂Python字符编码
Jan 23 Python
Python装饰器简单用法实例小结
Dec 03 Python
python后端接收前端回传的文件方法
Jan 02 Python
Numpy之random函数使用学习
Jan 29 Python
Python简直是万能的,这5大主要用途你一定要知道!(推荐)
Apr 03 Python
python-序列解包(对可迭代元素的快速取值方法)
Aug 24 Python
如何使用python实现模拟鼠标点击
Jan 06 Python
推荐8款常用的Python GUI图形界面开发框架
Feb 23 Python
Python Dict找出value大于某值或key大于某值的所有项方式
Jun 05 Python
Django CBV模型源码运行流程详解
Aug 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
Yii PHP Framework实用入门教程(详细介绍)
2013/06/18 PHP
PHP使用Session遇到的一个Permission denied Notice解决办法
2014/07/30 PHP
几道坑人的PHP面试题 试试看看你会不会也中招
2014/08/19 PHP
php实现基于openssl的加密解密方法
2016/09/30 PHP
总结PHP代码规范、流程规范、git规范
2018/06/18 PHP
php实现QQ小程序发送模板消息功能
2019/09/18 PHP
索趣科技的答案
2007/02/07 Javascript
可以支持多中格式的JS键盘
2007/05/02 Javascript
Javascript+XMLHttpRequest+asp.net无刷新读取数据库数据
2009/08/09 Javascript
jQuery 位置函数offset,innerWidth,innerHeight,outerWidth,outerHeight,scrollTop,scrollLeft
2010/03/23 Javascript
关于juqery radio写法的兼容性问题(新老版本jquery)
2010/06/14 Javascript
Javascript 多浏览器兼容总结(实战经验)
2013/10/30 Javascript
用JQuery实现全选与取消的两种简单方法
2014/02/22 Javascript
jQuery中height()方法用法实例
2014/12/24 Javascript
jQuery事件委托之Safari
2016/07/05 Javascript
angular-ngSanitize模块-$sanitize服务详解
2017/06/13 Javascript
Angular2 自定义validators的实现方法
2017/07/05 Javascript
详解JS中的this、apply、call、bind(经典面试题)
2017/09/19 Javascript
微信小程序富文本渲染引擎的详解
2017/09/30 Javascript
详解如何使用 vue-cli 开发多页应用
2017/12/16 Javascript
详解Angular调试技巧之报错404(not found)
2018/01/31 Javascript
javascript异步处理与Jquery deferred对象用法总结
2019/06/04 jQuery
Vue中this.$nextTick的作用及用法
2020/02/04 Javascript
Python 的字典(Dict)是如何存储的
2019/07/05 Python
Django shell调试models输出的SQL语句方法
2019/08/29 Python
基于Python获取照片的GPS位置信息
2020/01/20 Python
详解如何用HTML5 Canvas API控制图片的缩放变换
2016/03/22 HTML / CSS
html5中audio支持音频格式的解决方法
2018/08/24 HTML / CSS
Holiday Inn中国官网:IHG旗下假日酒店预订
2018/04/08 全球购物
罗兰·穆雷官网:Roland Mouret
2018/09/28 全球购物
运动会跳远广播稿5篇
2014/09/17 职场文书
2015年统计员个人工作总结
2015/07/23 职场文书
2015年街道办事处团委工作总结
2015/10/14 职场文书
青年岗位能手事迹材料(2016推荐版)
2016/03/01 职场文书
2016年暑期社会实践活动总结报告
2016/04/06 职场文书
python实现简单倒计时功能
2021/04/21 Python