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生成日历实例解析
Aug 21 Python
详解python中的线程
Feb 10 Python
Python多项式回归的实现方法
Mar 11 Python
pandas 数据结构之Series的使用方法
Jun 21 Python
Python调用百度根据经纬度查询地址的示例代码
Jul 07 Python
python Manager 之dict KeyError问题的解决
Dec 21 Python
python dataframe NaN处理方式
Dec 26 Python
Python面向对象封装操作案例详解 II
Jan 02 Python
Pytorch在NLP中的简单应用详解
Jan 08 Python
Python3实现监控新型冠状病毒肺炎疫情的示例代码
Feb 13 Python
Python抓包并解析json爬虫的完整实例代码
Nov 03 Python
python 实现的IP 存活扫描脚本
Dec 10 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
迅雷下载《中学科技》怀旧期刊下载
2021/02/27 无线电
Dwz与thinkphp整合下的数据导出到Excel实例
2014/12/04 PHP
[原创]PHP简单开启curl的方法(测试可行)
2016/01/11 PHP
PHP实现的微信APP支付功能示例【基于TP5框架】
2019/09/16 PHP
DOM 基本方法
2009/07/18 Javascript
JavaScript 拾碎[三] 使用className属性
2010/10/16 Javascript
Jquery中删除元素的实现代码
2011/12/29 Javascript
用nodejs写的一个简单项目打包工具
2013/05/11 NodeJs
jQuery中操控hidden、disable等无值属性的方法
2014/01/06 Javascript
JS获取数组最大值、最小值及长度的方法
2015/11/24 Javascript
基于jQuery.validate及Bootstrap的tooltip开发气泡样式的表单校验组件思路详解
2016/07/18 Javascript
DataTables+BootStrap组合使用Ajax来获取数据并且动态加载dom的方法(排序,过滤,分页等)
2016/11/09 Javascript
Jil,高效的json序列化和反序列化库
2017/02/15 Javascript
websocket+node.js实现实时聊天系统问题咨询
2017/05/17 Javascript
MUI顶部选项卡的用法(tab-top-webview-main)详解
2017/10/08 Javascript
Vue2.0学习之详解Vue 组件及父子组件通信
2017/12/12 Javascript
每天学点Vue源码之vm.$mount挂载函数
2019/03/11 Javascript
VuePress 中如何增加用户登录功能
2019/11/29 Javascript
antd日期选择器禁止选择当天之前的时间操作
2020/10/29 Javascript
[04:16]DOTA2全国高校联赛16强抽签
2018/05/02 DOTA
举例讲解Python中的迭代器、生成器与列表解析用法
2016/03/20 Python
Python爬虫包 BeautifulSoup  递归抓取实例详解
2017/01/28 Python
Python中按值来获取指定的键
2019/03/04 Python
python简单鼠标自动点击某区域的实例
2019/06/25 Python
Python-openCV读RGB通道图实例
2020/01/17 Python
pytorch随机采样操作SubsetRandomSampler()
2020/07/07 Python
python 实现音频叠加的示例
2020/10/29 Python
屈臣氏俄罗斯在线商店:Watsons俄罗斯
2020/08/03 全球购物
python+selenium小米商城红米K40手机自动抢购的示例代码
2021/03/24 Python
酒店门卫岗位职责
2013/12/29 职场文书
实习协议书范本
2014/04/22 职场文书
民族团结先进集体事迹材料
2014/05/22 职场文书
财政局党的群众路线教育实践活动整改方案
2014/09/21 职场文书
会计求职自荐信范文
2015/03/04 职场文书
公司晚会主持词
2019/04/17 职场文书
python中pycryto实现数据加密
2022/04/29 Python