学习Python有一段时间了,最近做一个项目会涉及到文件的读取和关闭。比如:我想把一些对象序列化到文件里面,然后当我再次使用的时候,在从文件里面读取反序列化成对象。像这种操作一般都是用try…except…finally。但是经过自己对Python的研究发现会有更出色的方法,比如:with-as语句也有的人称为context manager。

With-as 用法

我们先看一下例子,当我们需要打开一个文件的时,比如:txt等,一般经常会这么操作:

try:f = file.open('test.txt','rw')To Do
except:To Do
finally:f.close()

这是错误,因为file.open是否打开文件是不确定,而在出现异常的时候你却关闭了已经打开的文件。文件没有打开怎么能直接关闭呢?你可以按照下面的解决方法来解决上述出现的问题。

try:  f = file.open('test.txt','rw')To Do
except:  To Do//出现异常直接返回或者退出,这说明file并没有打开。return/exit(-1) //已经成功打开file文件,所以你需要在finally中关闭打开的文件。
try:  To Do
except:  To Do
finally:  f.close()

你会发现这么做会非常麻烦,并且try……except…..finally嵌套也比较啰嗦。那有没有好的解决办法能解决上述问题,并且还能减少代码量呢?(类似于C#中的using关键字)答案是肯定的,那就是with……as语句。With语句适用于对I/O、文件流、数据流等资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源.比如文件使用后自动关闭、线程中锁的自动获取和释放等等。 我们来看一下with-as的用法:

with open('/etc/passwd', 'r') as f:  for line in f:print line... more processing code ...

这个语句执行完成之后,不管在处理文件过程中是否发生异常,都能保证 with 语句执行完毕后已经关闭了打开的文件句柄,确实比try……except……finally好多了。在这个例子中f就是上下文管理器enter()的返回值,返回的是当前文件自身的引用。Python内建对象都加入了对上下文管理器的支持,可以用在with语句中。比如:file、 threading、decimal等等,在多线程模块中,lock和条件变量也是支持with语句的。例如:

lock = threading.Lock()
with lock:  # Critical section of code...

在代码执行之前lock总是先获得,只要block代码完成lock就会被释放。要想彻底了解Python的With-As语句,请继续往下看。

Python术语

Context Management Protocol(上下文管理协议):包含方法__enter()__和__exit()__,支持该协议的对象要实现这两个方法。上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter()__ 和__exit()__ 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。

context_manager = context_expression
exit = type(context_manager).__exit__
value = type(context_manager).__enter__(context_manager)
# True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理.
exc = True  try:target = value  # 如果使用了 as 子句with-body     # 执行 with-body
except:# 执行过程中有异常发生exc = False# 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常# 由外层代码对异常进行处理if not exit(context_manager, *sys.exc_info()):raise
finally:# 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出# 或者忽略异常退出if exc:exit(context_manager, None, None, None) # 缺省返回 None,None 在布尔上下文中看做是 False

上下文管理协议

with表达式执行生成一个叫做上下文管理器的对象,上下文管理器必须包含enter()和exit()方法,并且要实现该两个方法。 上下文管理器的enter()方法被调用,返回值将赋值给var,如果没有as var,则返回值被丢弃。 执行With-Body语句体。 不管是否执行过程中是否发生了异常,执行上下文管理器的 exit() 方法,exit() 方法负责执行“clean-up”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句( break/continue/return),则以 None 作为参数调用 exit(None, None, None) ;如果执行过程中出现异常,则使用 sys.excinfo 得到的异常信息为参数调用 exit(exctype, excvalue, exctraceback),通常返回值是一个tuple, (type, value/message, traceback)。 出现异常时,如果 exit(type, value, traceback) 返回 False,则会重新抛出异常,让with 之外的语句逻辑来处理异常,这也是通用做法;如果返回 True,则忽略异常,不再对异常进行处理。 运行时上下文(runtime context):通过上下文管理器创建,并由上下文管理器的 enter() 和exit() 方法实现,enter() 方法在语句体执行之前进入运行时上下文,exit() 在语句体执行完后从运行时上下文退出。返回一个布尔值表示是否对发生的异常进行处理。如果退出时没有发生异常,则3个参数都为(None,None,None)。如果发生异常,返回True :不处理异常,否则会在退出该方法后重新抛出异常以由 with 语句之外的代码进行处理。如果该方法内部产生异常,不能重新抛出通过参数传递进来的异常,只需要return False 就可以。之后,上下文管理代码会检测是否 exit() 失败来捕获和处理异常。

#-*- coding: utf-8 -*-class Cursor(object):  def execute(self,msg):print msgclass DatabaseConnection(object):  def commit(self):print "Commits current transaction"def rollback(self):print "Rolls back current transaction"def __enter__(self):print "Go into __enter__()"cursor = Cursor()return cursordef __exit__(self,exc_type,exc_value,exc_tb):print "Go into __exit__()"#raise Exception("__exit__......Exception")if exc_tb is None:#如果没有异常,则提交事务print "Exited Without Exception"self.commit()else:#如果有异常,则回滚print "Exited with exception raised"print "type:[",exc_type,"],value:[",exc_value,"],exc_tb:[",exc_tb,"]"self.rollback()if __name__=="__main__":  db_connection = DatabaseConnection()with db_connection as cursor:cursor.execute("insert into......")cursor.execute("delete from......")

代码运行效果如下:

Go into __enter__()
insert into......
delete from......
Go into __exit__()
Exited Without Exception
Commits current transaction

上述代码正好验证了我们之前的分析,当运行with dbconnection运行时,进入我们自定义的__enter()__方法,当执行完with包裹的代码块时,就会进入__exit()__方法,如果没有异常(通过exctb是否为None来判断,当然也可以用其他两个参数判断。)则执行相应的代码逻辑。

#!/usr/bin/env python
#-*- coding: utf-8 -*-class Cursor(object):  def execute(self,msg):print msgclass DatabaseConnection(object):  def commit(self):print "Commits current transaction"def rollback(self):print "Rolls back current transaction"def __enter__(self):print "Go into __enter__()"cursor = Cursor()return cursordef __exit__(self,exc_type,exc_value,exc_tb):print "Go into __exit__()"#raise Exception("__exit__......Exception")if exc_tb is None:#如果没有异常,则提交事务print "Exited Without Exception"self.commit()else:#如果有异常,则回滚print "Exited With Exception raised"print "type:[",exc_type,"],value:[",exc_value,"],exc_tb:[",exc_tb,"]"self.rollback()if __name__=="__main__":  db_connection = DatabaseConnection()with db_connection as cursor:cursor.execute("insert into......")raise Exception("raise exception")cursor.execute("delete from......")

该代码示例中,我在with包裹的代码块中造成一个异常。我们来看一下效果:

Go into __enter__()
insert into......
Go into __exit__()
Exited With Exception raised
type:[ <type 'exceptions.Exception'> ],value:[ raise exception ],exc_tb:[ <traceback object at 0x0252A878> ]
Rolls back current transaction
Traceback (most recent call last):  File "D:\Test\DatabaseConnection.py", line 34, in <module>raise Exception("raise exception")
Exception: raise exception

当with包裹的代码块一旦出现异常,则进入exit()方法内,并根据该方法的参数全不为None。如果你在exit方法内你不手动返回一个值的话,则默认返回False。如果你返回True,则不会捕捉该异常,即使你在with代码块最外面包裹一个try……except…finally也不会捕捉到该异常,如果返回False则with之外的try–except也能捕捉到。

#!/usr/bin/env python
#-*- coding: utf-8 -*-import sysclass Cursor(object):  def execute(self,msg):print msgclass DatabaseConnection(object):  def commit(self):print "Commits current transaction"def rollback(self):print "Rolls back current transaction"def __enter__(self):print "Go into __enter__()"cursor = Cursor()return cursordef __exit__(self,exc_type,exc_value,exc_tb):print "Go into __exit__()"#raise Exception("__exit__......Exception")if exc_tb is None:#如果没有异常,则提交事务print "Exited Without Exception"self.commit()else:#如果有异常,则回滚print "Exited With Exception raised"print "type:[",exc_type,"],value:[",exc_value,"],exc_tb:[",exc_tb,"]"self.rollback()return Trueif __name__=="__main__":  db_connection = DatabaseConnection()try:with db_connection as cursor:cursor.execute("insert into......")raise Exception("raise exception")cursor.execute("delete from......")except:print"包裹with语句的try ",sys.exc_info()

运行效果如下:

Go into __enter__()
insert into......
Go into __exit__()
Exited With Exception raised
type:[ <type 'exceptions.Exception'> ],value:[ raise exception ],exc_tb:[ <traceback object at 0x0252A878> ]
Rolls back current transaction

基本上Python的常用的用法我就了解这么多,至于代码希望你动手试一下你也能了解Python with语句的原理.

The contextlib module

contextlib模块支持一些函数和装饰器,比如:装饰器 contextmanager、函数 nested 和上下文管理器closing。使用这些对象,可以对已有的生成器(yield)函数或者对象进行包装,加入对上下文管理协议的支持,这样可以避免专门编写上下文管理器来支持 with 语句。

contextmanager

contextmanager 用于对生成器(yield)函数进行装饰,生成器(yield)函数被装饰以后,返回的是一个ContextManager(上下文管理器),其 __enter()__ 和 __exit()__ 方法由 contextmanager 负责提供,而不是之前通过一个上下文管理器重写这两个方法了。被装饰的函数只能产生一个值,否则会导致异常 RuntimeError;并且会把yield的值赋值给as后面的变量。看一下例子:

#!/usr/bin/env python
#-*- coding: utf-8 -*-import sys
from contextlib import contextmanagerclass Cursor(object):  def execute(self,msg):print msgclass DatabaseConnection(object):  def cursor(self):print "Create a instance of Cursor"return Cursor()def commit(self):print "Commits current transaction"def rollback(self):print "Rolls back current transaction"@contextmanager
def db_transaction(connection):  cursor = connection.cursor()try:print "yeild 执行之前......"yield cursorprint "yeild 执行之后......"except:print "db_transaction raise exception"connection.rollback()else:print"Existed without exception"connection.commit()if __name__=="__main__":  db_connection = DatabaseConnection()try:with db_transaction(db_connection) as cursor:cursor.execute("insert into......")#raise Exception("raise exception")cursor.execute("delete from......")except:print sys.exc_info()

运行结果如下:

Create a instance of Cursor
yeild 执行之前......
insert into......
delete from......
yeild 执行之后......
Existed without exception
Commits current transaction

通过上述运行结果,可以看出,生成器函数中 yield 之前的语句在 enter() 方法中执行,yield 之后的语句在__exit()__ 中执行,而 yield 产生的值赋给了 as 后面的 变量。这个contextmanager修饰器 只是省略了 __enter()__ / __exit()__ 的编写,但并不负责实现“获取资源”和“清理资源”工作;“获取资源”操作需要定义在 yield 语句之前,“清理资源”操作需要定义 yield 语句之后,这样 with 语句在执行__enter()__ / __exit()__ 方法时会执行这些语句以获取/释放资源,即生成器函数中需要实现必要的逻辑控制,包括资源访问出现错误时抛出适当的异常。 nested(mgr1, mgr2, …)() contextlib模块也有个nested(mgr1, mgr2, …)()函数,这个函数可以作用在多个上下文管理器。例如下面的例子with 语句不仅开启一个transaction也获得了一个线程锁,让当前操作不被其他线程干扰。

#!/usr/bin/env python
#-*- coding: utf-8 -*-import sys
from contextlib import contextmanager,nested,closing
import threadingclass Cursor(object):  def execute(self,msg):print msgclass DatabaseConnection(object):  def cursor(self):print "Create a instance of Cursor"return Cursor()def commit(self):print "Commits current transaction"def rollback(self):print "Rolls back current transaction"@contextmanager
def db_transaction(connection):  cursor = connection.cursor()try:print "yeild 执行之前......"yield cursorprint "yeild 执行之后......"except:print "db_transaction raise exception"connection.rollback()else:print"Existed without exception"connection.commit()if __name__=="__main__":  db_connection = DatabaseConnection()lock = threading.Lock()with nested(db_transaction(db_connection),lock) as (cursor,locked):cursor.execute("insert into......")cursor.execute("delete from......")
运行结果如下:Create a instance of Cursor
yeild 执行之前......
insert into......
delete from......
yeild 执行之后......
Existed without exception
Commits current transaction

closing(object)()方法返回一个对象可以绑定到as后面的变量,保证了打开的对象在 with-body(with包裹的代码) 执行完后会关闭掉。closing 上下文管理器包装起来的对象必须提供 close() 方法的定义,否则执行时会报错误。closing 适用于提供了 close() 实现的对象,比如:网络连接、数据库连接有非常的用武之地,也可以在自定义类时通过接口 close() 来执行所需要的资源“清理”工作。当然上述的这些操作你完全可以按照自己的逻辑去执行。请看如下代码:

import urllib, sys
from contextlib import closingwith closing(urllib.urlopen('http://www.yahoo.com')) as f:  for line in f:sys.stdout.write(line)

基本上python的With语句的理解到此结束。希望本文对你有用,如有用请推荐。

参考文献

  • 浅谈 Python 的 with 语句 这篇文章写得不错,里面的术语解释的很到位,本文中用到的术语多来源于这篇文章。
  • Python 2.6 官方介绍言简意赅突出主题,文中的例子来源于这篇文章。自己把它实现了啊。
  • 理解Python中的with…as…语法可以参考一下,这篇文章作者介绍的还可以,但是没有给出更全面的介绍没有第一个文章中术语介绍的详细。
  • 理解Python的with语句只是一般介绍没有深入。

原文来源:https://cloud.tencent.com/developer/article/1083148

深入理解Python的With-as语句相关推荐

  1. python中if语句怎么结束_if语句,让你透彻理解python中的if语句(限小白)

    传统化交易还是有些人性无法避免的弊端,这个时候,我们就要借助计算机进行程序化交易,让我们的交易走得更高更远.而程序化交易必备的知识是Python,下面我们讲解下python中的if语句. 只要你耐心看 ...

  2. 深入理解Python中的if语句

    公众号:尤而小屋 作者:Peter 编辑:Peter 大家好,我是Peter~ 在生活中总是会听到这样的话: 如果我上课认真一点,英语肯定可以及格 如果我努力锻炼,肯定可以减到100斤 如果我技术过硬 ...

  3. 理解python的with as 语句

    转自点击打开链接 <python标准库>上这么一句话: with open('filename', 'wt') as f:f.write('hello, world!') 我不明白为什么这 ...

  4. python语句解释_深入理解python with 语句

    深入理解python with 语句 python中with 语句作为try/finally 编码范式的一种替代, 适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的" ...

  5. python中return的理解-Python return语句 函数返回值

    return语句是从python 函数返回一个值,在讲到定义函数的时候有讲过,每个函数都要有一个返回值.Python中的return语句有什么作用,今天就来仔细的讲解一下. python 函数返回值 ...

  6. 如何用python制作九九乘法表_“九九乘法表”案例理解python循环语句

    这是树哥讲python的第七篇文章. 在所有的编程语言中,循环语句是最常用的语句之一,因为循环语句可以充分发挥计算机运算速度优点,很多人做起来无法忍受的重复,对于计算机来讲轻松完成.所以今天,我们就是 ...

  7. Python中的 if 语句简单介绍,中英文完整理解

    来学习Python中的if语句,用英文如何表达?看看else究竟是留还是省略? Python 中的if语句可以省略else吗?if 后面的表达式求值结果是什么类型?

  8. python装饰器原理-深刻理解python装饰器

    我们要完全理解python装饰器,不是很容易,主要归结有如下困难: 1. 关于函数"变量"(或"变量"函数)的理解 2. 关于高阶函数的理解 3. 关于嵌套函数 ...

  9. python怎么导入包-如何理解Python中包的引入

    Python的from import *和from import *,它们的功能都是将包引入使用,但是它们是怎么执行的以及为什么使用这种语法呢? 从一模块导入全部功能 from import * me ...

最新文章

  1. 如何彻底解决安装Windows漏洞补丁出现蓝屏或无法启动问题?
  2. 迪拜宣布亿航“无人机的士”计划,将于7月份正式运营
  3. 【jQuery】使用jquery.form.js,获取提交表单返回值
  4. 解读最新的 Xamarin 更新
  5. Javascript模拟c#中arraylist操作(学习分享)
  6. mysql 函数修改无效_MySQL:无效使用组函数
  7. 通风与防排烟工程电子书_工厂防排烟系统与通风空调系统的兼用设计
  8. LA2218半平面交
  9. VB连接SQL SERVER实例
  10. 【axure手机原型】移动产品的设计思路谈
  11. 老师常用选择题,选择框,单选框,以及各行业产品配置表单选配置明细等
  12. C语言中 \0 代表什么?
  13. 微信整人假红包图片_微信整人红包动态图如何制作 微信红包图片显示5秒后变成恶搞图或其它文字制作方法...
  14. 计算机通识培训,通识培训研修日志
  15. SMAA算法详解 - SMAAEdgeDetectionVS
  16. 计算机课程教学调查问卷,《初中生信息技术课程学习现状调查问卷》分析报告...
  17. SpringBoot + Java生成证书
  18. 《强化学习周刊》第55期:LB-SGD、MSP-DRL对抗鲁棒强化学习
  19. Windows查看ios手机日志
  20. 通读cheerio API

热门文章

  1. .NET程序员的C\C++情结(3)
  2. Pidgin Portable 使用点滴
  3. 你了解微服务架构么?
  4. Oracle database datafile header中记录的datafile的大小
  5. ES6新特性5:类(Class)和继承(Extends)
  6. python 压缩文件(1)
  7. 《卓有成效的管理者》——读书笔记
  8. 十年研发经验嵌入式工程师书籍推荐
  9. 传统云主机存在哪些问题和弊端?
  10. 专用DNS的CDN工作流程