强大的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,因为没什么有用的对象能提供给用户。
1
2
|
with open ( 'mirror.py' ) as fp: ... |
自定义的上下文类:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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方法时)执行==。
1
2
3
4
5
6
7
8
9
10
11
|
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 块中做什么。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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' ) |
强大的with语句的更多相关文章
- 批处理学习:for语句详解【经典】
大纲 一 前言 二 for语句的基本用法 三 for /f (delims.tokens.skip.eol.userbackq.变量延迟) 四 for /r (递归遍历) 五 for /d (遍历目录 ...
- 【转】dos下 和 批处理中的 for 语句的基本用法
for 语句的基本用法 : 最复杂的for 语句,也有其基本形态,它的模样是这样的: 在cmd 窗口中:for %I in (command1) do command2 在批处理文件中:for % ...
- dos下 和 批处理中的 for 语句的基本用法
for 语句的基本用法 : 最复杂的for 语句,也有其基本形态,它的模样是这样的: 在cmd 窗口中:for %I in (command1) do command2 在批处理文件中:for % ...
- 批处理学习:for语句详解
大纲 一 前言 二 for语句的基本用法 三 for /f (delims.tokens.skip.eol.userbackq.变量延迟) 四 for /r (递归遍历) 五 for /d (遍历目录 ...
- Try-Catch-Finally语句块执行问题
Try-Catch-Finally语句块执行问题 记录一个今天某公司的面试问题,其实我问题回答对了,但是面试官问我动手验证过没有,这还真没有,纯理论,被怼惨了,希望自己能变得更强大. Try-Catc ...
- 《Android游戏开发详解》一1.7 控制流程第1部分——if和else语句
本节书摘来异步社区<Android游戏开发详解>一书中的第1章,第1.7节,译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社区"异步社区"公众号查看. 1.7 ...
- Prometheus 系统监控方案 一
最近一直在折腾时序类型的数据库,经过一段时间项目应用,觉得十分不错.而Prometheus又是刚刚推出不久的开源方案,中文资料较少,所以打算写一系列应用的实践过程分享一下. Prometheus 是什 ...
- Python爬虫学习(9):Selenium的使用
1 简介以及安装 Selenium 是什么?一句话,自动化测试工具.它支持各种浏览器,包括 Chrome,Safari,Firefox 等主流界面式浏览器,如果你在这些浏览器里面安装一个 Seleni ...
- 复杂sql分组查询 ( pivot)
一个数据表里面字段有年.月.日.金额.支付方式等字段,然后现在想写个sql语句,把每一天的每种支付方式金额(支付方式有多重)排在同一行, 最后在增加一列小计当前的所有支付方式的金额.如下图: 原sql ...
随机推荐
- Java热启动
1.在项目中的pom.xml中添加 <dependency> <groupId>org.springframework.boot</groupId> <art ...
- springboot 学习笔记(二)
springboot 学习笔记(二) 快速创建一个springboot工程,并引入所需要的依赖 1.利用Spring initializr 来创建一个springboot项目,登陆http://sta ...
- mysql(数据库,sql语句,普通查询)
第1章 数据库 1.1 数据库概述 l 什么是数据库 数据库就是存储数据的仓库,其本质是一个文件系统,数据按照特定的格式将数据存储起来,用户可以对数据库中的数据进行增加,修改,删除及查询操作. l 什 ...
- 织梦后台上传mp4视频不显示
include/dialog/select_media.php ,约185行, 把(rm|rmvb) 改为(rm|mp4|rmvb)
- Arduino ESP8266编程深入要点
Arduino for ESP8266的话,如果不修改代码,默认没有办法进入轻睡眠的省电模式,只能进入Modem Sleep,也就是说Wifi可以暂时睡眠但是CPU没法睡,Modem Sleep最低功 ...
- GIT SSH免登录密码实现更新(git pull)、推送(git push)操作
一.使用场景 现在有两台服务器A和B,在A服务器上搭建有git版本代码仓库,现要实现B服务器SSH免密码登录A服务器,并能够从A服务器拉取.推送代码! 二.操作步骤 1.在B服务器项目根目录下执行以 ...
- CentOS7.2上安装Python3.6
CentOS 7下安装Python3.6 1)安装python3.6可能使用的依赖yum -y install openssl-devel bzip2-devel expat-devel gdbm-d ...
- C语言中字符串数组的遍历和比较
/* The list of known types of default packet. */static char *_default_packet_types[] = { "d ...
- CDOJ 485 UESTC 485 Game (八数码变形,映射,逆cantor展开)
题意:八数码,但是转移的方式是转动,一共十二种,有多组询问,初态唯一,终态不唯一. 题解:初态唯一,那么可以预处理出012345678的所有转移情况,然后将初态对012345678做一个映射,再枚举一 ...
- python基础一 day15 面试题
# def demo():# for i in range(4):# yield i## g=demo()## g1=(i for i in g)# g2=(i for i in g1)## prin ...