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使用Flask框架同时上传多个文件的方法
Mar 21 Python
python编写简单爬虫资料汇总
Mar 22 Python
Python中operator模块的操作符使用示例总结
Jun 28 Python
Python中的字符串操作和编码Unicode详解
Jan 18 Python
Python3 queue队列模块详细介绍
Jan 05 Python
Python cookbook(数据结构与算法)找到最大或最小的N个元素实现方法示例
Feb 13 Python
Python代码块批量添加Tab缩进的方法
Jun 25 Python
使用Template格式化Python字符串的方法
Jan 22 Python
Python运行提示缺少模块问题解决方案
Apr 02 Python
Python自动化测试中yaml文件读取操作
Aug 20 Python
Python脚本调试工具安装过程
Jan 11 Python
用Python监控你的朋友都在浏览哪些网站?
May 27 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版微信公众平台红包API
2015/04/02 PHP
PHP实现的简单对称加密与解密方法实例小结
2017/08/28 PHP
使用laravel的migrate创建数据表的方法
2019/09/30 PHP
JS类定义原型方法的两种实现的区别评论很多
2007/09/12 Javascript
javascript中数组的多种定义方法和常用函数简介
2014/05/09 Javascript
JavaScript实现弹出模态窗体并接受传值的方法
2016/02/12 Javascript
js阻止默认浏览器行为与冒泡行为的实现代码
2016/05/15 Javascript
JQueryEasyUI框架下的combobox的取值和绑定的方法
2017/01/22 Javascript
微信小程序多张图片上传功能
2017/06/07 Javascript
jQuery实现QQ空间汉字转拼音功能示例
2017/07/10 jQuery
微信小程序倒计时功能实现代码
2017/11/09 Javascript
Angular2学习笔记之数据绑定的示例代码
2018/01/03 Javascript
浅谈vue中改elementUI默认样式引发的static与assets的区别
2018/02/03 Javascript
node.js遍历目录的方法示例
2018/08/01 Javascript
Vue实现左右菜单联动实现代码
2018/08/12 Javascript
vue中promise的使用及异步请求数据的方法
2018/11/08 Javascript
微信公众号生成新浪短网址的实现(快速生成)
2019/08/18 Javascript
jquery实现淡入淡出轮播图效果
2020/12/13 jQuery
[01:11:15]VGJ.S vs Secret 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
Python3实现发送QQ邮件功能(附件)
2020/12/23 Python
python PyTorch预训练示例
2018/02/11 Python
解决Python安装后pip不能用的问题
2018/06/12 Python
Python之用户输入的实例
2018/06/22 Python
Win8.1下安装Python3.6提示0x80240017错误的解决方法
2018/07/31 Python
python实现动态数组的示例代码
2019/07/15 Python
python selenium登录豆瓣网过程解析
2019/08/10 Python
python 有效的括号的实现代码示例
2019/11/11 Python
python主线程与子线程的结束顺序实例解析
2019/12/17 Python
美丽的珠宝配饰:SmallThings
2019/09/04 全球购物
英国名牌男装店:Standout
2021/02/17 全球购物
不用游标的SQL语句有哪些
2012/09/07 面试题
如何通过jdbc调用存储过程
2012/04/19 面试题
高中三年学习生活的自我评价
2013/10/10 职场文书
营业员演讲稿
2013/12/30 职场文书
道士塔读书笔记
2015/06/30 职场文书
公司环境卫生管理制度
2015/08/05 职场文书