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中线程问题的简单讲解
Apr 03 Python
Python base64编码解码实例
Jun 21 Python
Python求解任意闭区间的所有素数
Jun 10 Python
python 字典 按key值大小 倒序取值的实例
Jul 06 Python
Django 路由控制的实现代码
Nov 08 Python
使用python实现语音文件的特征提取方法
Jan 09 Python
在Python运行时动态查看进程内部信息的方法
Feb 22 Python
Python猴子补丁知识点总结
Jan 05 Python
django实现模型字段动态choice的操作
Apr 01 Python
tensorflow使用CNN分析mnist手写体数字数据集
Jun 17 Python
Python绘图之二维图与三维图详解
Aug 04 Python
如何利用Python给自己的头像加一个小国旗(小月饼)
Oct 02 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获取$_POST同名参数数组的实现介绍
2013/06/30 PHP
php class类的用法详细总结
2013/10/17 PHP
值得分享的php+ajax实时聊天室
2016/07/20 PHP
php 删除指定文件夹的实例讲解
2017/07/25 PHP
PHP实现断点续传乱序合并文件的方法
2018/09/06 PHP
PHP日期和时间函数的使用示例详解
2020/08/06 PHP
javascript 子窗体父窗体相互传值方法
2010/05/31 Javascript
js里取容器大小、定位、距离等属性搜集整理
2013/08/19 Javascript
javaScript如何生成xmlhttp
2013/12/16 Javascript
js控制input框只读实现示例
2014/01/20 Javascript
点击表单提交时出现jQuery没有权限的解决方法
2014/07/23 Javascript
JS函数的定义与调用方法推荐
2016/05/12 Javascript
js验证框架实现代码分享
2016/05/18 Javascript
Boostrap基础教程之JavaScript插件篇
2016/09/08 Javascript
初探nodeJS
2017/01/24 NodeJs
Vue.js实现移动端短信验证码功能
2017/03/29 Javascript
ubuntu编译nodejs所需的软件并安装
2017/09/12 NodeJs
Vue.js项目中管理每个页面的头部标签的两种方法
2018/06/25 Javascript
解决vue点击控制单个样式的问题
2018/09/05 Javascript
vue 使用鼠标滚动加载数据的例子
2019/10/31 Javascript
node.js express捕获全局异常的三种方法实例分析
2019/12/27 Javascript
vue 实现用户登录方式的切换功能
2020/04/14 Javascript
vue-cli4.x创建企业级项目的方法步骤
2020/06/18 Javascript
vue-cli+webpack项目打包到服务器后,ttf字体找不到的解决操作
2020/08/28 Javascript
Node.js fs模块原理及常见用途
2020/10/22 Javascript
Python操作redis和mongoDB的方法
2019/12/19 Python
树莓派升级python的具体步骤
2020/07/05 Python
多视角3D可旋转的HTML5 Logo动画
2016/03/02 HTML / CSS
德国百年厨具品牌WMF美国站:WMF美国
2016/09/12 全球购物
澳大利亚在线高跟鞋商店:Shoe Me
2019/11/19 全球购物
德国消费电子产品购物网站:Guter Kauf
2020/09/15 全球购物
后勤人员自我鉴定
2013/10/20 职场文书
男性健康日的活动方案
2014/08/18 职场文书
搞笑的婚礼主持词
2015/06/29 职场文书
MySQL分区表管理命令汇总
2022/03/21 MySQL
Vue+Flask实现图片传输功能
2022/04/01 Vue.js