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实现的简单队列以及跨进程锁实例详解
Jul 07 Python
Python中的super用法详解
May 28 Python
Python简单计算文件夹大小的方法
Jul 14 Python
Python编程实现数学运算求一元二次方程的实根算法示例
Apr 02 Python
Python2.7+pytesser实现简单验证码的识别方法
Dec 29 Python
Python实现的堆排序算法示例
Apr 29 Python
Python分析彩票记录并预测中奖号码过程详解
Jul 09 Python
Python调用C语言的实现
Jul 26 Python
Python 类方法和实例方法(@classmethod),静态方法(@staticmethod)原理与用法分析
Sep 20 Python
python路径的写法及目录的获取方式
Dec 26 Python
python tqdm 实现滚动条不上下滚动代码(保持一行内滚动)
Feb 19 Python
Python基于Dlib的人脸识别系统的实现
Feb 26 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 程序员的调试技术小结
2009/11/15 PHP
php header Content-Type类型小结
2011/07/03 PHP
PHP实现图片压缩
2020/09/09 PHP
Js中sort()方法的用法
2006/11/04 Javascript
javascript call和apply方法
2008/11/24 Javascript
JQuery学习笔记 nt-child的使用
2011/01/17 Javascript
读jQuery之三(构建选择器)
2011/06/11 Javascript
解决Jquery load()加载GB2312页面时出现乱码的两种方案
2013/09/10 Javascript
jquery事件与函数的使用介绍
2013/09/29 Javascript
jquery 操作iframe的几种方法总结
2013/12/13 Javascript
推荐阅读的js快速判断IE浏览器(兼容IE10与IE11)
2015/12/13 Javascript
快速掌握WordPress中加载JavaScript脚本的方法
2015/12/17 Javascript
js实现获取两个日期之间所有日期的方法
2016/06/17 Javascript
BootStrap table使用方法分析
2016/11/08 Javascript
Bootstrap Table使用整理(四)之工具栏
2017/06/09 Javascript
vue生命周期实例小结
2018/08/15 Javascript
基于JS实现数字动态变化显示效果附源码
2019/07/18 Javascript
浅谈Vue3.0之前你必须知道的TypeScript实战技巧
2019/09/11 Javascript
JS实现联想、自动补齐国家或地区名称的功能
2020/07/07 Javascript
JS如何调用WebAssembly编译出来的.wasm文件
2020/11/05 Javascript
[05:41]2014DOTA2西雅图国际邀请赛 小组赛7月10日TOPPLAY
2014/07/10 DOTA
[36:22]VP vs Serenity 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
[43:24]VG vs Serenity 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/20 DOTA
python实现自动获取IP并发送到邮箱
2018/12/26 Python
Python 3.3实现计算两个日期间隔秒数/天数的方法示例
2019/01/07 Python
python集合的创建、添加及删除操作示例
2019/10/08 Python
CSS3动画之利用requestAnimationFrame触发重新播放功能
2019/09/11 HTML / CSS
css3中仿放大镜效果的几种方式原理解析
2020/12/03 HTML / CSS
美国高档帽子网上商店:Hats.com
2018/08/09 全球购物
Python中pass语句的作用是什么
2016/06/01 面试题
学生党员思想汇报范文
2014/01/09 职场文书
霸王洗发水广告词
2014/03/14 职场文书
2014年便民服务中心工作总结
2014/12/20 职场文书
合作意向书范本
2019/04/17 职场文书
HTML5页面音频自动播放的实现方式
2021/06/21 HTML / CSS
Python通过loop.run_in_executor执行同步代码 同步变为异步
2022/04/11 Python