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使用cx_Oracle调用Oracle存储过程的方法示例
Oct 07 Python
Python使用MD5加密算法对字符串进行加密操作示例
Mar 30 Python
python计算列表内各元素的个数实例
Jun 29 Python
Python自定义一个类实现字典dict功能的方法
Jan 19 Python
opencv3/Python 稠密光流calcOpticalFlowFarneback详解
Dec 11 Python
Pytorch之Variable的用法
Dec 31 Python
利用matplotlib为图片上添加触发事件进行交互
Apr 23 Python
Python Flask框架实现简单加法工具过程解析
Jun 03 Python
Opencv求取连通区域重心实例
Jun 04 Python
详解python程序中的多任务
Sep 16 Python
python中time tzset()函数实例用法
Feb 18 Python
Opencv中cv2.floodFill算法的使用
Jun 18 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中error_reporting()函数的用法(修改PHP屏蔽错误)
2011/07/01 PHP
php中mysql模块部分功能的简单封装
2011/09/30 PHP
Function eregi is deprecated (解决方法)
2013/06/21 PHP
php按百分比生成缩略图的代码分享
2014/05/10 PHP
Smarty局部缓存的几种方法简介
2014/06/17 PHP
30个php操作redis常用方法代码例子
2014/07/05 PHP
PHP中使用虚代理实现延迟加载技术
2014/11/05 PHP
php解析字符串里所有URL地址的方法
2015/04/03 PHP
详谈PHP程序Laravel 5框架的优化技巧
2016/07/18 PHP
laravel-admin 管理平台获取当前登陆用户信息的例子
2019/10/08 PHP
Flash+XML滚动新闻代码 无图片 附源码下载
2007/11/22 Javascript
JavaScript高级程序设计 读书笔记之十一 内置对象Global
2012/03/07 Javascript
jquery实现非叠加式的搜索框提示效果
2014/01/07 Javascript
javascript实现在下拉列表中显示多级树形菜单的方法
2015/08/12 Javascript
个人网站留言页面(前端jQuery编写、后台php读写MySQL)
2016/05/03 Javascript
Bootstrap打造一个左侧折叠菜单的系统模板(二)
2016/05/17 Javascript
js数字计算 误差问题的快速解决方法
2017/02/28 Javascript
移动设备手势事件库Touch.js使用详解
2017/08/18 Javascript
为输入框加入数字js校验代码分享
2017/11/02 Javascript
微信小程序switch组件使用详解
2018/01/31 Javascript
对vuejs的v-for遍历、v-bind动态改变值、v-if进行判断的实例讲解
2018/08/27 Javascript
vue + typescript + 极验登录验证的实现方法
2019/06/27 Javascript
[01:11]steam端dota2实名认证操作流程视频
2021/03/11 DOTA
python中子类调用父类函数的方法示例
2017/08/18 Python
python实现求解列表中元素的排列和组合问题
2018/03/15 Python
Sanic框架蓝图用法实例分析
2018/07/17 Python
python Tensor和Array对比分析
2020/01/08 Python
python中doctest库实例用法
2020/12/31 Python
Python 的 f-string 可以连接字符串与数字的原因解析
2021/02/20 Python
Square Off美国/加拿大:世界上最聪明的国际象棋棋盘
2018/12/06 全球购物
运动鞋、足球鞋和慕尼黑球衣:Sport Münzinger
2019/08/26 全球购物
信息专业毕业生五年职业规划参考
2014/02/06 职场文书
酒店管理专业毕业生求职自荐信
2014/04/28 职场文书
承诺书模板
2014/08/30 职场文书
邀请函怎么写
2015/01/30 职场文书
从贫穷到富有,是知识技能和学习力的差别
2019/08/20 职场文书