首页 > 编程笔记

Python with as语句的正确用法

任何一门编程语言中,文件的输入输出、数据库的连接断开等,都是很常见的资源管理操作。但资源是有限的,在编写代码时,必须保证这些资源在使用后得到释放,不然容易造成资源泄漏,轻者使得系统处理缓慢,严重时会使系统崩溃。

为了更好地避免此类问题,不同的编程语言引入了不同的机制。在 Python 中,对应的解决方式是使用 with as 语句操作上下文管理器,上下文管理器能够帮助我们自动分配并且释放资源。

有一些任务可能事先需要设置,事后做清理工作。对于这种场景,Python 的 with as 语句提供了一种非常方便的处理方式。例如,使用 with as 语句操作已经打开的文件对象,无论程序运行期间是否抛出异常,都保证 with as 语句执行完毕后自动关闭已经打开的文件。

除了有更优雅的语法,with as 语句还可以很好地处理上下文环境产生的异常。

with as 语句通过 __enter__() 方法初始化,然后在 __exit__() 中做善后和异常处理,所以使用 with 处理的对象必须有 __enter__() 和 __exit__() 这两个方法。

Python 中 with as 语句的语法格式如下:
with expression [as target]:
    with_body
参数说明如下:
with as 语句的执行流程如下:
示例如下:
#!/usr/bin/env python
class Sample:
    def __enter__(self):
        return self
    def __exit__(self, type, value, trace):
        print "type:", type
        print "value:", value
        print "trace:", trace
    def do_something(self):
        bar = 1/0
        return bar + 10
with Sample() as sample:
    sample.do_something()
在示例中,只要紧跟 with 后面的语句所返回的对象有 __enter__() 和 __exit__() 方法即可实现上下文资源的管理。此例中,Sample() 的 __enter__() 方法返回新创建的 Sample 对象,并赋值给变量 sample。

代码执行后输出如下:
type: <type 'exceptions.ZeroDivisionError'>
value: integer division or modulo by zero
trace: <traceback object at 0x1004a8128>
Traceback (most recent call last):
  File "./with_example02.py", line 19, in <module>
    sample.do_something()
  File "./with_example02.py", line 15, in do_something
    bar = 1/0
ZeroDivisionError: integer division or modulo by zero
实际上,在 with 的代码块抛出异常时,__exit__() 方法将被执行。正如示例中,异常抛出时,与之关联的 type、value 和 stack trace 传入 __exit__() 方法,因此抛出的 ZeroDivisionError 异常被输出。在开发模块时,清理资源、关闭文件等操作都可以放在 __exit__() 方法当中。

因此,Python 的 with as 语句提供了一个有效的让代码更简练的机制,同时让异常产生时的清理工作更简单。

此外,with as 语句支持嵌套多环境管理器,语法如下:
with A() as a, B() as b:
...statements...
它等价于嵌套的with as语句:
with A() as a:
  with B() as b:
  ...statements...
多环境管理器管理的多个对象会在 with as 语句的代码块出现异常时,或者执行完 with as 语句的代码块时全部自动被清理。

例如,打开两个文件,将它们的内容通过 zip() 合并在一起,然后同时关闭它们:
with open('a.file') as f1, open('b.file') as f2:
    for pair in zip(f1, f2):
        print(pair)

总结

Python 中的 with as 语句适用于需要访问资源的场景,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,以释放资源,例如文件使用后自动关闭、线程中锁的自动获取和释放等。

推荐阅读