Python 的 with 语句详解_python

一、简介

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

时间: 2024-09-19 03:22:21

Python 的 with 语句详解_python的相关文章

python之import机制详解_python

本文详述了Python的import机制,对于理解Python的运行机制很有帮助! 1.标准import: Python中所有加载到内存的模块都放在 sys.modules .当 import 一个模块时首先会在这个列表中查找是否已经加载了此模块,如果加载了则只是将模块的名字加入到正在调用 import 的模块的 Local 名字空间中.如果没有加载则从 sys.path 目录中按照模块名称查找模块文件,模块可以是py.pyc.pyd,找到后将模块载入内存,并加到 sys.modules 中,并

Python break语句详解_python

Python break语句,就像在C语言中,打破了最小封闭for或while循环.break语句用来终止循环语句,即循环条件没有False条件或者序列还没被完全递归完,也会停止执行循环语句.break语句用在while和for循环中.如果您使用嵌套循环,break语句将停止执行最深层的循环,并开始执行下一行代码. 一.Python语言 break 语句语法 复制代码 代码如下: break 二.逻辑流程图三.使用实例 复制代码 代码如下: #!/usr/bin/python for lette

python xml解析实例详解_python

python xml解析 first.xml  <info> <person > <id>1</id> <name>fsy</name> <age >24</age> </person> <person> <id>2</id> <name>jianjian</name> <age>24</age> </pers

python正则表达式re模块详解_python

快速入门 import re pattern = 'this' text = 'Does this text match the pattern?' match = re.search(pattern, text) s = match.start() e = match.end() print('Found "{0}"\nin "{1}"'.format(match.re.pattern, match.string)) print('from {0} to {1}

Python Deque 模块使用详解_python

创建Deque序列: from collections import deque d = deque() Deque提供了类似list的操作方法: d = deque() d.append('1') d.append('2') d.append('3') len(d) d[0] d[-1] 输出结果: 3 '1' '3' 两端都使用pop: d = deque('12345') len(d) d.popleft() d.pop() d 输出结果: 5 '1' '5' deque(['2', '3

打包发布Python模块的方法详解_python

前言 昨天把自己的VASP文件处理库进行了打包并上传到PyPI,现在可以直接通过pip和easy_install来安装VASPy啦(同时欢迎使用VASP做计算化学的童鞋们加星和参与进来), VASPy的GotHub地址:https://github.com/PytLab/VASPy VASPy的PyPI地址:https://pypi.python.org/pypi/vaspy/ 由于自己的记性真是不咋地,怕时间久了就忘了,于是在这里趁热打铁以自己的VASPy程序为例对python的打包和上传进行

利用Python破解验证码实例详解_python

一.前言 本实验将通过一个简单的例子来讲解破解验证码的原理,将学习和实践以下知识点:       Python基本知识       PIL模块的使用 二.实例详解 安装 pillow(PIL)库: $ sudo apt-get update $ sudo apt-get install python-dev $ sudo apt-get install libtiff5-dev libjpeg8-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev lib

Python冒泡排序注意要点实例详解_python

冒泡排序注意三点: 1. 第一层循环可不用循环所有元素. 2.两层循环变量与第一层的循环变量相关联. 3.第二层循环,最终必须循环集合内所有元素. 示例代码一: 1.第一层循环,只循环n-1个元素. 2.当第一层循环变量为n-1时,第二层循环所有元素. s = [3, 4, 1, 6, 2, 9, 7, 0, 8, 5] # bubble_sort for i in range(0, len(s) - 1): for j in range(i + 1, 0, -1): if s[j] < s[j

Python深入06——python的内存管理详解_python

语言的内存管理是语言设计的一个重要方面.它是决定语言性能的重要因素.无论是C语言的手工管理,还是Java的垃圾回收,都成为语言最重要的特征.这里以Python语言为例子,说明一门动态类型的.面向对象的语言的内存管理方式. 对象的内存使用 赋值语句是语言最常见的功能了.但即使是最简单的赋值语句,也可以很有内涵.Python的赋值语句就很值得研究. a = 1 整数1为一个对象.而a是一个引用.利用赋值语句,引用a指向对象1.Python是动态类型的语言(参考动态类型),对象与引用分离.Python