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操作Mysql实例代码教程在线版(查询手册)
Feb 18 Python
python uuid模块使用实例
Apr 08 Python
PyQt 线程类 QThread使用详解
Jul 16 Python
Python图像的增强处理操作示例【基于ImageEnhance类】
Jan 03 Python
python-tkinter之按钮的使用,开关方法
Jun 11 Python
对django views中 request, response的常用操作详解
Jul 17 Python
Django ImageFiled上传照片并显示的方法
Jul 28 Python
解决Django中调用keras的模型出现的问题
Aug 07 Python
基于pytorch的保存和加载模型参数的方法
Aug 17 Python
Python OpenCV视频截取并保存实现代码
Nov 30 Python
Tensorflow 使用pb文件保存(恢复)模型计算图和参数实例详解
Feb 11 Python
python属于跨平台语言码
Jun 09 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之字符串变相相减的代码
2007/03/19 PHP
PHP 日期时间函数的高级应用技巧
2009/10/10 PHP
PHP中array_slice函数用法实例详解
2014/11/25 PHP
PHP随机获取未被微信屏蔽的域名(微信域名检测)
2017/03/19 PHP
详解PHP神奇又有用的Trait
2019/03/25 PHP
推荐自用 Javascript 缩图函数 (onDOMLoaded)……
2007/10/23 Javascript
JSON 入门指南 想了解json的朋友可以看下
2009/08/26 Javascript
JSON 和 JavaScript eval使用说明
2010/06/13 Javascript
jquery.validate使用攻略 第一部
2010/07/01 Javascript
基于jquery实现的可以编辑选择的下拉框的代码
2010/11/19 Javascript
jquery中dom操作和事件的实例学习 仿yahoo邮箱登录框的提示效果
2011/11/30 Javascript
JavaScript验证图片类型(扩展名)的函数分享
2014/05/05 Javascript
jquery加载图片时以淡入方式显示的方法
2015/01/14 Javascript
Javascript闭包(Closure)详解
2015/05/05 Javascript
JS+CSS实现类似QQ好友及黑名单效果的树型菜单
2015/09/22 Javascript
适用于javascript开发者的Processing.js入门教程
2016/02/24 Javascript
jQuery绑定事件-多种实现方式总结
2016/05/09 Javascript
Vue.js每天必学之内部响应式原理探究
2016/09/07 Javascript
源码分析Vue.js的监听实现教程
2017/04/23 Javascript
使用JS组件实现带ToolTip验证框的实例代码
2017/08/23 Javascript
prototype.js简单实现ajax功能示例
2017/10/18 Javascript
JS实现获取进今年第几天是周几的方法分析
2018/06/27 Javascript
react-navigation之动态修改title的内容
2018/09/26 Javascript
angularjs实现table表格td单元格单击变输入框/可编辑状态示例
2019/02/21 Javascript
uniapp与webview之间的相互传值的实现
2020/06/29 Javascript
利用python打印出菱形、三角形以及矩形的方法实例
2017/08/08 Python
对django2.0 关联表的必填on_delete参数的含义解析
2019/08/09 Python
在pycharm中显示python画的图方法
2019/08/31 Python
Python目录和文件处理总结详解
2019/09/02 Python
Gretna Green中文官网:苏格兰格林小镇
2019/10/16 全球购物
主持词开场白
2014/03/17 职场文书
开学典礼演讲稿
2014/05/23 职场文书
项目经理任命书范本
2014/06/05 职场文书
工程资料员岗位职责
2015/04/13 职场文书
Python list去重且保持原顺序不变的方法
2021/04/03 Python
Mysql数据库表中为什么有索引却没有提高查询速度
2022/02/24 MySQL