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 09 Python
Python中的错误和异常处理简单操作示例【try-except用法】
Jul 25 Python
python爬虫_实现校园网自动重连脚本的教程
Apr 22 Python
Python中关键字global和nonlocal的区别详解
Sep 03 Python
Python判断一个三位数是否为水仙花数的示例
Nov 13 Python
python 同时运行多个程序的实例
Jan 07 Python
Python 虚拟空间的使用代码详解
Jun 10 Python
django的model操作汇整详解
Jul 26 Python
ORM Django 终端打印 SQL 语句实现解析
Aug 09 Python
使用Python将Exception异常错误堆栈信息写入日志文件
Apr 08 Python
Django中使用Json返回数据的实现方法
Jun 03 Python
如何理解python对象
Jun 21 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
Linux下进行MYSQL编程时插入中文乱码的解决方案
2007/03/15 PHP
PHP遍历XML文档所有节点的方法
2015/03/12 PHP
JavaScript网页制作特殊效果用随机数
2007/05/22 Javascript
JavaScript 组件之旅(三):用 Ant 构建组件
2009/10/28 Javascript
js动态添加onload、onresize、onscroll事件(另类方法)
2012/12/26 Javascript
原生Js实现元素渐隐/渐现(原理为修改元素的css透明度)
2013/06/24 Javascript
javaScript arguments 对象使用介绍
2013/10/18 Javascript
js控制再次点击按钮之间的间隔时间可防止重复提交
2014/08/01 Javascript
JavaScript实现的一个日期格式化函数分享
2014/12/06 Javascript
Javascript中String的常用方法实例分析
2015/06/13 Javascript
bootstrap datetimepicker2.3.11时间插件使用
2016/11/19 Javascript
整理一些最近经常遇到的前端面试题
2017/04/25 Javascript
关于vue中 $emit的用法详解
2018/04/12 Javascript
Vue中的异步组件函数实现代码
2018/07/20 Javascript
vue实现滑动切换效果(仅在手机模式下可用)
2020/06/29 Javascript
JavaScript实现滑动门效果
2020/01/18 Javascript
vue用elementui写form表单时,在label里添加空格操作
2020/08/13 Javascript
JavaScript代码实现微博批量取消关注功能
2021/02/05 Javascript
[05:03]2018DOTA2亚洲邀请赛主赛事首日回顾
2018/04/04 DOTA
python设置windows桌面壁纸的实现代码
2013/01/28 Python
python批量查询、汉字去重处理CSV文件
2018/05/31 Python
Python DataFrame.groupby()聚合函数,分组级运算
2018/09/18 Python
python中的句柄操作的方法示例
2019/06/20 Python
python代码xml转txt实例
2020/03/10 Python
Python3.9新特性详解
2020/10/10 Python
Html5 语法与规则简要概述
2014/07/29 HTML / CSS
生物科学专业职业规划书范文
2014/02/11 职场文书
百年校庆节目主持词
2014/03/27 职场文书
团委书记的竞聘演讲稿
2014/04/24 职场文书
教师党的群众路线对照检查材料
2014/09/24 职场文书
认错检讨书
2014/10/02 职场文书
介绍信的格式
2015/01/30 职场文书
排球赛新闻稿
2015/07/17 职场文书
SQL Server代理:理解SQL代理错误日志处理方法
2021/06/30 SQL Server
漫画《催眠麦克风-Dawn Of Divisions》第二卷PV公开
2022/04/05 日漫
【D4DJ】美少女DJ企划 动画将于明年冬季开播第2季
2022/04/11 日漫