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和Ruby中each循环引用变量问题(一个隐秘BUG?)
Jun 04 Python
Python与Redis的连接教程
Apr 22 Python
python实现爬虫统计学校BBS男女比例之多线程爬虫(二)
Dec 31 Python
Python基于Socket实现的简单聊天程序示例
Aug 05 Python
Python cookbook(数据结构与算法)从任意长度的可迭代对象中分解元素操作示例
Feb 13 Python
python实现批量按比例缩放图片效果
Mar 30 Python
python使用epoll实现服务端的方法
Oct 16 Python
python实现杨氏矩阵查找
Mar 02 Python
python字符串替换第一个字符串的方法
Jun 26 Python
基于python的列表list和集合set操作
Nov 24 Python
Python reshape的用法及多个二维数组合并为三维数组的实例
Feb 07 Python
django Model层常用验证器及自定义验证器详解
Jul 15 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 获取SWF动画截图示例代码
2014/02/10 PHP
javascript权威指南 学习笔记之null和undefined
2011/09/25 Javascript
jquery ajax,ashx,json的用法总结
2014/02/12 Javascript
JavaScript实现找出数组中最长的连续数字序列
2014/09/03 Javascript
常用的jquery模板插件——jQuery Boilerplate介绍
2014/09/23 Javascript
js代码实现随机颜色的小方块
2015/07/30 Javascript
jQuery实现仿QQ空间装扮预览图片的鼠标提示效果代码
2015/10/30 Javascript
jquery判断当前浏览器的实现代码
2015/11/07 Javascript
详解Angularjs filter过滤器
2016/02/06 Javascript
Vue.js报错Failed to resolve filter问题的解决方法
2016/05/25 Javascript
原生js实现打字动画游戏
2017/02/04 Javascript
Bootstrap表单制作代码
2017/03/17 Javascript
纯JS实现图片验证码功能并兼容IE6-8(推荐)
2017/04/19 Javascript
打字效果动画的4种实现方法(超简单)
2017/10/18 Javascript
Node 升级到最新稳定版的方法分享
2018/05/17 Javascript
JS/jQuery实现获取时间的方法及常用类完整示例
2019/03/07 jQuery
Python中用format函数格式化字符串的用法
2015/04/08 Python
以Flask为例讲解Python的框架的使用方法
2015/04/29 Python
详解python之简单主机批量管理工具
2017/01/27 Python
Python3学习urllib的使用方法示例
2017/11/29 Python
Python Tkinter实现简易计算器功能
2018/01/30 Python
Tensorflow矩阵运算实例(矩阵相乘,点乘,行/列累加)
2020/02/05 Python
pytorch 多分类问题,计算百分比操作
2020/07/09 Python
HTML5中外部浏览器唤起微信分享
2020/01/02 HTML / CSS
Kent & Curwen:与大卫·贝克汉姆合作
2017/06/13 全球购物
盖尔斯工厂店:GUESS Factory
2020/01/21 全球购物
自荐书4要点
2014/01/25 职场文书
党员实事承诺书
2014/03/26 职场文书
人大调研汇报材料
2014/08/14 职场文书
乡镇党建工作汇报材料
2014/10/27 职场文书
初三学生语文考试作弊检讨书
2014/12/14 职场文书
2015年安全生产责任书
2015/01/30 职场文书
辞职信如何写
2015/02/27 职场文书
施工现场安全管理制度
2015/08/05 职场文书
导游词之南京莫愁湖公园
2019/11/13 职场文书
Python Pandas 删除列操作
2022/03/16 Python