果冻想
认真玩技术的地方

Python中with用法详解

前言

断断续续玩Python也有一段时间了,都是玩的很业余,写一些小的工具程序,基本上都是停留在能用的层面上。后来呢,自己想吧,不能总是这样学习吧,就寻思着把Python好好的、系统的、全面的学习一下,所以就买了几本书,想着这五一大过节的在家里安安静静的学习学习,陶冶陶冶,深造深造。可谁曾想,不看不知道,一看吓一大跳,这刚翻开没几页,就好几个模糊的知识点,而这篇将要总结的with就是其中的一个。哎,不看了,撸串去~~~

抛出问题

之前写一些Python小工具,读写文件都是这样搞的:

#!/usr/bin/env python

fileReader = open('students.txt', 'r')

for row in fileReader:
    print(row.strip())

fileReader.close()

基本也实现了读取文件的功能。但是有的时候,上述代码在运行的时候会抛出异常,导致无法关闭文件句柄,这个时候,我就会加上异常处理程序,代码就改成了这样:

#!/usr/bin/env python

try:
    fileReader = open('students.txt', 'r')

    for row in fileReader:
        print(row.strip())
except:
    print('Read file failed')
finally:
    fileReader.close()

也还好,解决了抛出一大堆异常的问题。但是上述代码怎么看都有点累赘啰嗦的感觉。那怎么办?

解决问题

上面的代码虽然可以完成相应的功能,但是不简洁、很啰嗦,读起来也很麻烦。为了解决这种问题,从Python 2.5开始引入了with语句,一种与异常处理相关的功能。

with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。比如上面的代码,通过使用with语句改造,就变成了下面这个样子:

#!/usr/bin/env python

with open('students.txt', 'r') as fileReader:
    for row in fileReader:
        print(row.strip())

可以看到,通过使用with语句重构,代码立马明朗了不少。这里虽然说了with怎么使用,但是并没有说到点上,没法让人真的明白with的原理。

深挖原理

为了更好的掌握with语句的用法,明白其内在的原理。这里就深挖一下with语句的原理,让大家知其然,更知其所以然。

要搞清楚with语句的原理,先要说一下下面这几个概念:

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

说完上面两个概念,我们再从with语句的常用表达式入手,一段基本的with表达式,其结构是这样的:

with EXPR as VAR:
    BLOCK

其中EXPR可以是任意表达式;as VAR是可选的。其一般的执行过程是这样的:

  1. 执行EXPR,生成上下文管理器context_manager;
  2. 获取上下文管理器的__exit()__方法,并保存起来用于之后的调用;
  3. 调用上下文管理器的__enter__()方法;如果使用了as子句,则将__enter__()方法的返回值赋值给as子句中的VAR;
  4. 执行BLOCK中的表达式;
  5. 不管是否执行过程中是否发生了异常,执行上下文管理器的__exit__()方法,__exit__()方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句break/continue/return,则以None作为参数调用__exit__(None, None, None);如果执行过程中出现异常,则使用sys.exc_info得到的异常信息为参数调用__exit__(exc_type, exc_value, exc_traceback)
  6. 出现异常时,如果__exit__(type, value, traceback)返回False,则会重新抛出异常,让with之外的语句逻辑来处理异常,这也是通用做法;如果返回True,则忽略异常,不再对异常进行处理。

自定义上下文管理器

讲完了with语句的内在原理,接下来我们就可以按照这个原理,实现我们自己的上下文管理器。自定义的上下文管理器要实现上下文管理协议所需要的__enter__()__exit__()两个方法:

#!/usr/bin/env python

class DBManager(object):
    def __init__(self):
        pass

    def __enter__(self):
        print('__enter__')
        return self

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

def getInstance():
        return DBManager()

with getInstance() as dbManagerIns:
    print('with demo')

代码运行结果如下:

__enter__
with demo
__exit__

这样我们就可以很轻松的自定义上下文管理器来对软件系统中的资源进行管理,比如数据库连接、共享资源的访问控制等。

总结

这篇文章通过实际的问题入手,分析问题,到最终的解决问题,环环相扣,通过通熟易懂的语言把Python中的with语句进行了比较全面的总结,希望对大家的Python学习之路有所帮助。

果冻想,认真玩技术的地方。

2019年5月3日,于内蒙古包头。

赞(5) 打赏
未经允许不得转载:果冻想 » Python中with用法详解
关注微信公众号
关注微信公众号和果冻一起分享你的疑惑与心得。
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

玩技术,我们是认真的

联系我们关于果冻

请我喝杯咖啡也是不错的

支付宝扫一扫打赏

微信扫一扫打赏