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中的ctime()方法使用教程
May 22 Python
遍历python字典几种方法总结(推荐)
Sep 11 Python
Python构建网页爬虫原理分析
Dec 19 Python
python使用mysql的两种使用方式
Mar 07 Python
Django restframework 框架认证、权限、限流用法示例
Dec 21 Python
python手写均值滤波
Feb 19 Python
opencv python在视屏上截图功能的实现
Mar 05 Python
解决Keras中循环使用K.ctc_decode内存不释放的问题
Jun 29 Python
Python文件夹批处理操作代码实例
Jul 21 Python
详解python爬取弹幕与数据分析
Nov 14 Python
Python Pycharm虚拟下百度飞浆PaddleX安装报错问题及处理方法(亲测100%有效)
May 24 Python
python​格式化字符串
Apr 20 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 读取文件头判断文件类型的实现代码
2013/08/05 PHP
php实现的Cookies操作类实例
2014/09/24 PHP
PHP中的output_buffering详细介绍
2014/09/27 PHP
Jquery 基础学习笔记之文档处理
2009/05/29 Javascript
Javascript 面向对象特性
2009/12/28 Javascript
解析offsetHeight,clientHeight,scrollHeight之间的区别
2013/11/20 Javascript
使用JS+plupload直接批量上传图片到又拍云
2014/12/01 Javascript
基于jquery实现放大镜效果
2015/08/17 Javascript
avalon js实现仿google plus图片多张拖动排序附源码下载
2015/09/24 Javascript
无限循环轮播图之运动框架(原生JS实现)
2017/10/01 Javascript
Django与Vue语法的冲突问题完美解决方法
2017/12/14 Javascript
jQuery实现动态加载select下拉列表项功能示例
2018/05/31 jQuery
JavaScript创建对象方法实例小结
2018/09/03 Javascript
iView-admin 动态路由问题的解决方法
2018/10/03 Javascript
原生js实现获取form表单数据代码实例
2019/03/27 Javascript
JavaScript常用内置对象用法分析
2019/07/09 Javascript
关于vue项目中搜索节流的实现代码
2019/09/17 Javascript
python中关于时间和日期函数的常用计算总结(time和datatime)
2013/03/08 Python
Python threading多线程编程实例
2014/09/18 Python
Python实现简单的可逆加密程序实例
2015/03/05 Python
python 表达式和语句及for、while循环练习实例
2017/07/07 Python
Python安装模块的常见问题及解决方法
2018/02/05 Python
python 对key为时间的dict排序方法
2018/10/17 Python
python2和python3的输入和输出区别介绍
2018/11/20 Python
简单了解Pandas缺失值处理方法
2019/11/16 Python
python/golang 删除链表中的元素
2020/09/14 Python
Giglio德国网上精品店:奢侈品服装和配件
2016/09/23 全球购物
汽车专业人才自我鉴定范文
2013/12/29 职场文书
2014年健康教育实施方案
2014/02/17 职场文书
农村面貌改造提升实施方案
2014/03/18 职场文书
体检通知范文
2015/04/21 职场文书
彻底理解golang中什么是nil
2021/04/29 Golang
微信小程序中使用vant框架的具体步骤
2022/02/18 Javascript
Win11怎么修改电源模式?Win11修改电源模式的方法
2022/04/05 数码科技
Java由浅入深通关抽象类与接口(下篇)
2022/04/26 Java/Android
使用CSS实现六边形的图片效果
2022/08/05 HTML / CSS