Python 的 with 语句详解


Posted in Python onJune 13, 2014

一、简介

with是从Python 2.5 引入的一个新的语法,更准确的说,是一种上下文的管理协议,用于简化try…except…finally的处理流程。with通过__enter__方法初始化,然后在__exit__中做善后以及处理异常。对于一些需要预先设置,事后要清理的一些任务,with提供了一种非常方便的表达。

with的基本语法如下,EXPR是一个任意表达式,VAR是一个单一的变量(可以是tuple),”as VAR”是可选的。

with EXPR as VAR:
    BLOCK

根据PEP 343的解释,with…as…会被翻译成以下语句:
mgr = (EXPR)
exit = type(mgr).__exit__  # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
    try:
        VAR = value  # Only if "as VAR" is present
        BLOCK
    except:
        # The exceptional case is handled here
        exc = False
        if not exit(mgr, *sys.exc_info()):
            raise
        # The exception is swallowed if exit() returns true
finally:
    # The normal and non-local-goto cases are handled here
    if exc:
        exit(mgr, None, None, None)

为什么这么复杂呢?注意finally中的代码,需要BLOCK被执行后才会执行finally的清理工作,因为当EXPR执行时抛出异常,访问mgr.exit执行就会报AttributeError的错误。

二、实现方式

根据前面对with的翻译可以看到,被with求值的对象必须有一个__enter__方法和一个__exit__方法。稍微看一个文件读取的例子吧,注意在这里我们要解决2个问题:文件读取异常,读取完毕后关闭文件句柄。用try…except一般会这样写:

f = open('/tmp/tmp.txt')
try:
    for line in f.readlines():
        print(line)
finally:
    f.close()

注意我们这里没有处理文件打开失败的IOError,上面的写法可以正常工作,但是对于每个打开的文件,我们都要手动关闭文件句柄。如果要使用with来实现上述功能,需要需要一个代理类:
class opened(object):
    def __init__(self, name):
        self.handle = open(name)
    def __enter__(self):
        return self.handle
    def __exit__(self, type, value, trackback):
        self.handle.close()
with opened('/tmp/a.txt') as f:
    for line in f.readlines():
        print(line)

注意我们定了一个名字叫opened的辅助类,并实现了__enter__和__exit__方法,__enter__方法没有参数,__exit__方法的3个参数,分别代表异常的类型、值、以及堆栈信息,如果没有异常,3个入参的值都为None。

如果你不喜欢定义class,还可以用Python标准库提供的contextlib来实现:

from contextlib import contextmanager
@contextmanager
def opened(name):
    f = open(name)
    try:
        yield f
    finally:
        f.close()
with opened('/tmp/a.txt') as f:
    for line in f.readlines():
        print(line)

使用contextmanager的函数,yield只能返回一个参数,而yield后面是处理清理工作的代码。在我们读取文件的例子中,就是关闭文件句柄。这里原理上和我们之前实现的类opened是相同的,有兴趣的可以参考一下contextmanager的源代码。

三、应用场景

废话了这么多,那么到底那些场景下该使用with,有没有一些优秀的例子?当然啦,不然这篇文章意义何在。以下摘自PEP 343。

一个确保代码执行前加锁,执行后释放锁的模板:

@contextmanager
    def locked(lock):
        lock.acquire()
        try:
            yield
        finally:
            lock.release()
    with locked(myLock):
        # Code here executes with myLock held.  The lock is
        # guaranteed to be released when the block is left (even
        # if via return or by an uncaught exception).

数据库事务的提交和回滚:
@contextmanager
        def transaction(db):
            db.begin()
            try:
                yield None
            except:
                db.rollback()
                raise
            else:
                db.commit()

重定向stdout:
@contextmanager
def stdout_redirected(new_stdout):
    save_stdout = sys.stdout
    sys.stdout = new_stdout
    try:
        yield None
    finally:
        sys.stdout = save_stdout
with opened(filename, "w") as f:
    with stdout_redirected(f):
        print "Hello world"

注意上面的例子不是线程安全的,再多线程环境中要小心使用。

四、总结

with是对try…expect…finally语法的一种简化,并且提供了对于异常非常好的处理方式。在Python有2种方式来实现with语法:class-based和decorator-based,2种方式在原理上是等价的,可以根据具体场景自己选择。

with最初起源于一种block…as…的语法,但是这种语法被很多人所唾弃,最后诞生了with,关于这段历史依然可以去参考PEP-343和PEP-340

Python 相关文章推荐
python实现的重启关机程序实例
Aug 21 Python
运动检测ViBe算法python实现代码
Jan 09 Python
python实现学生信息管理系统
Apr 05 Python
Python运行不显示DOS窗口的解决方法
Oct 22 Python
pyqt5之将textBrowser的内容写入txt文档的方法
Jun 21 Python
centos7之Python3.74安装教程
Aug 15 Python
详解Python图像处理库Pillow常用使用方法
Sep 02 Python
TensorFlow tensor的拼接实例
Jan 19 Python
浅谈keras中的目标函数和优化函数MSE用法
Jun 10 Python
keras 回调函数Callbacks 断点ModelCheckpoint教程
Jun 18 Python
python爬虫要用到的库总结
Jul 28 Python
Matplotlib绘制条形图的方法你知道吗
Mar 21 Python
python学习笔记:字典的使用示例详解
Jun 13 #Python
Python urlopen()函数 示例分享
Jun 12 #Python
python教程之用py2exe将PY文件转成EXE文件
Jun 12 #Python
Python struct模块解析
Jun 12 #Python
深度剖析使用python抓取网页正文的源码
Jun 11 #Python
python k-近邻算法实例分享
Jun 11 #Python
浅析python 内置字符串处理函数的使用方法
Jun 11 #Python
You might like
基于PHP与XML的PDF文档生成技术
2006/10/09 PHP
adodb与adodb_lite之比较
2006/12/31 PHP
分页详解 从此分页无忧(PHP+mysql)
2007/11/23 PHP
《PHP编程最快明白》第七讲:php图片验证码与缩略图
2010/11/01 PHP
PHP图片库imagemagick安装方法
2014/09/23 PHP
PHP产生不重复随机数的5个方法总结
2014/11/12 PHP
PHP面向对象程序设计之命名空间与自动加载类详解
2016/12/02 PHP
PHP实现微信小程序用户授权的工具类示例
2019/03/05 PHP
锋利的jQuery 要点归纳(二) jQuery中的DOM操作(下)
2010/03/23 Javascript
javascript 节点遍历函数
2010/03/28 Javascript
jQuery中on()方法用法实例详解
2015/02/06 Javascript
JQuery中DOM事件绑定用法详解
2015/06/13 Javascript
js获取及修改网页背景色和字体色的方法
2015/12/29 Javascript
第九篇Bootstrap导航菜单创建步骤详解
2016/06/21 Javascript
Javascript json object 与string 相互转换的简单实现
2016/09/27 Javascript
React 使用browserHistory项目访问404问题解决
2018/06/01 Javascript
angularjs1.X 重构controller 的方法小结
2019/08/15 Javascript
three.js 将图片马赛克化的示例代码
2020/07/31 Javascript
Vue 简单实现前端权限控制的示例
2020/12/25 Vue.js
python操作 hbase 数据的方法
2016/12/18 Python
python opencv实现信用卡的数字识别
2020/01/12 Python
Python运行DLL文件的方法
2020/01/17 Python
将python字符串转化成长表达式的函数eval实例
2020/05/11 Python
adidas菲律宾官网:adidas PH
2020/02/07 全球购物
大门门卫岗位职责
2013/11/30 职场文书
高中物理教学反思
2014/02/08 职场文书
活动总结模板
2014/05/09 职场文书
心理咨询承诺书
2014/05/20 职场文书
单位委托书格式范本
2014/09/29 职场文书
同学聚会通知书
2015/04/20 职场文书
员工表扬信怎么写
2015/05/05 职场文书
运动会宣传稿50字
2015/07/23 职场文书
Golang 空map和未初始化map的注意事项说明
2021/04/29 Golang
linux下安装redis图文详细步骤
2021/12/04 Redis
通过T-SQL语句创建游标与实现数据库加解密功能
2022/03/16 SQL Server
2022新作动画《福星小子》释出宣传影片 加入内田真礼&宫野真守配音演出
2022/04/08 日漫