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的内存泄漏及gc模块的使用分析
Jul 16 Python
Python3指定路径寻找符合匹配模式文件
May 22 Python
python如何把嵌套列表转变成普通列表
Mar 20 Python
Django添加sitemap的方法示例
Aug 06 Python
python批量复制图片到另一个文件夹
Sep 17 Python
利用django+wechat-python-sdk 创建微信服务器接入的方法
Feb 20 Python
程序员的七夕用30行代码让Python化身表白神器
Aug 07 Python
Python在OpenCV里实现极坐标变换功能
Sep 02 Python
pycharm安装及如何导入numpy
Apr 03 Python
浅谈pycharm导入pandas包遇到的问题及解决
Jun 01 Python
完美解决pycharm 不显示代码提示问题
Jun 02 Python
使用pd.merge表连接出现多余行的问题解决
Jun 16 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简单开启curl的方法(测试可行)
2016/01/11 PHP
php处理复杂xml数据示例
2016/07/11 PHP
PHP脚本自动识别验证码查询汽车违章
2016/12/20 PHP
PHP读取、解析eml文件及生成网页的方法示例
2017/09/04 PHP
php常用经典函数集锦【数组、字符串、栈、队列、排序等】
2019/08/23 PHP
利用PHP计算有多少小于当前数字的数字方法示例
2020/08/26 PHP
可输入的下拉框
2006/06/19 Javascript
超级退弹代码
2008/07/07 Javascript
childNodes.length与children.length的区别
2009/05/14 Javascript
Extjs学习笔记之九 数据模型(上)
2010/01/11 Javascript
javascript css styleFloat和cssFloat
2010/03/15 Javascript
基于jquery实现图片广告轮换效果代码
2011/07/07 Javascript
ASP.NET jQuery 实例3 (在TextBox里面阻止复制、剪切和粘贴事件)
2012/01/13 Javascript
JS过滤url参数特殊字符的实现方法
2013/12/24 Javascript
js不能获取隐藏的div的宽度只能先显示后获取
2014/09/04 Javascript
jQuery实时显示鼠标指针位置和键盘ASCII码
2016/03/28 Javascript
深入浅析JS的数组遍历方法(推荐)
2016/06/15 Javascript
JavaScipt选取文档元素的方法(推荐)
2016/08/05 Javascript
html+javascript+bootstrap实现层级多选框全层全选和多选功能
2017/03/09 Javascript
基于Vue实现timepicker
2017/04/25 Javascript
简述vue中的config配置
2018/01/23 Javascript
vue-router项目实战总结篇
2018/02/11 Javascript
javascript使用substring实现的展开与收缩文字功能示例
2019/06/17 Javascript
jQuery实现简单弹幕效果
2019/11/28 jQuery
[43:24]2018DOTA2亚洲邀请赛3月29日 小组赛A组 LGD VS Liquid
2018/03/30 DOTA
Python多线程编程(五):死锁的形成
2015/04/05 Python
在Python中使用列表生成式的教程
2015/04/27 Python
Python的MongoDB模块PyMongo操作方法集锦
2016/01/05 Python
Python缩进和冒号详解
2016/06/01 Python
Python实现感知器模型、两层神经网络
2017/12/19 Python
python实现Floyd算法
2018/01/03 Python
python实现批量按比例缩放图片效果
2018/03/30 Python
CSS3弹性盒模型开发笔记(三)
2016/04/26 HTML / CSS
优秀通讯员事迹材料
2014/01/28 职场文书
校园安全主题班会
2015/08/12 职场文书
Redis配置外网可访问(redis远程连接不上)的方法
2022/12/24 Redis