0%

python with-as statement

Guide

file example

读取文件处理传统写法,需要确保文件正常打开之后,即使出现异常,也能够正常关闭。写法不够优雅。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try:
file = open("./12.txt")
except:
print("can not open file")
exit(1)

try:
data = file.read()
# ... do somethings
except:
print("exception")
finally:
print("OK. close file")
file.close()

with语句时用于对try except finally 的优化,让代码更加美观。同时确保即使执行异常,finally中的语句也可以执行。

with-as 重写

1
2
with open("./1.txt") as file:
data = file.read()

with-as 原理

基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法。

原理:

  1. with后面的语句被求值后返回对象A;
  2. 调用对象A的__enter__()方法将返回值赋给as后面的变量B;
  3. 执行with后面的代码块;
  4. 当with后面的代码块全部被执行完之后(或者由于抛出异常没有正常习执行完毕),最终都会调用A对象的__exit__()方法进行清理工作。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Sample:
def __enter__(self):
print "In __enter__()"
return "Foo"

def __exit__(self, type, value, trace):
print "In __exit__()"

def get_sample():
return Sample()

with get_sample() as s:
print "sample:", s

output

In __enter__()
sample: Foo
In __exit__()

steps:

  1. 执行with后面的语句get_sample()返回对象sample
  2. 执行sample对象的__enter__()方法,返回string类型的”Foo”赋值给as后面的s变量
  3. 执行代码块,打印变量”sample”的值为 “Foo”
  4. 代码块执行完毕,调用sample对象的__exit__()方法进行清理工作

with 处理异常

with除了可以进行清理工作之外,真正强大之处是可以处理异常。Sample类的__exit__方法有三个参数:val, typetrace。 这些参数在异常处理中相当有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()

output

type: <type 'exceptions.ZeroDivisionError'>
value: integer division or modulo by zero
trace: <traceback object at 0x0000000004A5AA08>

ZeroDivisionErrorTraceback (most recent call last)
<ipython-input-9-282d3906c5ac> in <module>()
     13 
     14 with Sample() as sample:
---> 15     sample.do_something()

<ipython-input-9-282d3906c5ac> in do_something(self)
      9 
     10     def do_something(self):
---> 11         bar = 1/0
     12         return bar + 10
     13 

ZeroDivisionError: integer division or modulo by zero

在执行代码块sample.do_something()的时候由于异常抛出,与之关联的type,value和stack trace传给__exit__()方法,因此抛出的ZeroDivisionError异常被打印出来了。

file 对象

python中的file对象已经实现了__enter__()__exit__()方法

1
file = open("./1.txt")
1
file.__enter__()
<open file './1.txt', mode 'r' at 0x00000000049FF030>
1
file.read(1)
1
file.__exit__()
1
file.read(1)
ValueErrorTraceback (most recent call last)

<ipython-input-19-d0e1662399bb> in <module>()
----> 1 file.read(1)


ValueError: I/O operation on closed file

Reference

History

  • 20181023: created.