with语句支持在一个叫上下文管理器的对象的控制下执行一系列语句,语法大概如下:
1
2
|
with context as var:
statements
|
其中的context必须是个上下文管理器,它实现了两个方法__enter__,__exit__。
1.需求是怎么产生的
在正常的管理各种系统资源(文件、锁定和连接),在涉及到异常时通常是个棘手的问题。异常很可能导致控制流跳过负责释放关键资源的语句。
看一段简单的文件写入代码:
1
2
3
4
5
|
filename
=
'my_file.txt'
f
=
open
(filename,
'w'
)
f.write(
'Hello '
)
f.write(
'World'
)
f.close()
|
如果发生了意外的情况,例如写入'World'时磁盘空间不足,就会抛出异常,那么close()语句将不会被执行。
一般的解决方案是使用try-finally语句:
1
2
3
4
5
6
7
|
try
:
filename
=
'my_file.txt'
f
=
open
(filename,
'w'
)
f.write(
'Hello '
)
f.write(
'World'
)
finally
:
f.close()
|
但随着语句的增多,try-finally显然不够简洁,用with-as语句可以很简洁的实现以上功能:
1
2
3
|
with
open
(
'my_file'
,
'w'
) as f:
f.write(
'Hello '
)
f.write(
'World'
)
|
这样不仅能处理出现异常的情况,而且还避免了在open一个文件后忘记了写close()方法的情况。
线程中的锁其实也实现了上下文管理协议:
1
2
3
4
5
|
import
threading
with threading.Lock():
# 关键部分
statements
# 关键部分结束
|
再也不怕忘记将锁释放了。
2.上下文管理器的实现
上下文管理器要实现__enter__和__exit__的特殊方法。
__enter__(self): 进入上下文管理器时调用此方法,其返回值将被放入with-as语句中as说明符指定的变量中。
__exit__(self,type,value,tb):离开上下文管理器调用此方法。如果有异常出现,type、value、tb分别为异常的类型、值和追踪信息。如果没有异常,
3个参数均设为None。此方法返回值为True或者False,分别指示被引发的异常得到了还是没有得到处理。如果返回False,引发的异常会被传递出上下文。
文件上下文管理协议大概是如下实现的:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class
OpenFile(
object
):
def
__init__(
self
,filename,mode):
self
.filename
=
filename
self
.mode
=
mode
def
__enter__(
self
):
self
.f
=
open
(
self
.filename,
self
.mode)
return
self
.f
#作为as说明符指定的变量的值
def
__exit__(
self
,
type
,value,tb):
self
.f.close()
return
False
#异常会被传递出上下文
with OpenFile(
'my_file.txt'
,
'w'
) as f:
f.write(
'Hello '
)
f.write(
'World'
)
|
相当于
1
2
3
4
|
try
:
执行__enter__的内容
finally
:
执行__exit__的内容
|
3.异常的处理
__exit__函数就能够拿到关于异常的所有信息(异常类型,异常值以及异常追踪信息),这些信息将帮助异常处理操作。
1
2
3
4
5
6
7
8
9
10
|
class
ListTrans(
object
):
def
__init__(
self
,alist):
self
.alist
=
alist
def
__enter__(
self
):
self
.thecopy
=
list
(
self
.alist)
return
self
.thecopy
def
__exit__(
self
,exc_type,value,tb):
if
exc_type
is
None
:
self
.alist[:]
=
self
.thecopy
return
False
|
没有异常发生时:
1
2
3
4
5
|
alist
=
[]
with ListTrans(alist) as working:
working.append(
1
)
working.append(
2
)
print
alist
|
生成:
1
|
[
1
,
2
]
|
有异常发生时:
1
2
3
4
5
6
|
alist
=
[]
with ListTrans(alist) as working:
working.append(
1
)
working.append(
2
)
raise
RuntimeError(
'we are hosed'
)
print
alist
|
生成:
1
|
RuntimeError: we are hosed
|
alist无变化。
可以捕捉异常:
1
2
3
4
5
6
7
8
9
|
alist
=
[]
try
:
with ListTrans(alist) as working:
working.append(
1
)
working.append(
2
)
raise
RuntimeError(
'we are hosed'
)
except
RuntimeError as e:
print
e
print
alist
|
生成:
1
2
|
we are hosed
[]
|
当然,也可以简单的将__exit__的返回值设为True来忽略异常。
4.contextmanager装饰器
@contextmanager
contextlib模块的contextmanager装饰器可以更方便的实现上下文管理器。
任何能够被yield关键词分割成两部分的函数,都能够通过装饰器装饰的上下文管理器来实现。任何在yield之前的内容都可以看做在代码块执行前的操作,
而任何yield之后的操作都可以放在exit函数中。
1
2
3
4
5
6
7
8
9
10
11
|
from
contextlib
import
contextmanager
@contextmanager
def
listTrans(alist):
thecopy
=
list
(alist)
yield
thecopy
alist[:]
=
thecopy
alist
=
[]
with listTrans(alist) as working:
working.append(
1
)
working.append(
2
)
print
alist
|
yield返回的值相当于__enter__的返回值。
要注意的是,这不是异常安全的写法,也就是说,当出现异常时,yield后的语句是不会执行的,想要异常安全,可用try捕捉异常:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from
contextlib
import
contextmanager
@contextmanager
def
listTrans(alist):
thecopy
=
list
(alist)
try
:
yield
thecopy
except
RuntimeError:
pass
alist[:]
=
thecopy
alist
=
[]
with listTrans(alist) as working:
working.append(
1
)
working.append(
2
)
raise
RuntimeError
|
nested与closing
contextlib模块还有两个好玩的方法:nested,closing。
nested:用来更方便的减少嵌套写法:
当要嵌套的写上下文管理器时:
1
2
3
|
with
open
(
'toReadFile'
,
'r'
) as reader:
with
open
(
'toWriteFile'
,
'w'
) as writer:
writer.writer(reader.read())
|
可以用nested简化写法:
1
2
3
|
with contextlib.nested(
open
(
'fileToRead.txt'
,
'r'
),
open
(
'fileToWrite.txt'
,
'w'
)) as (reader, writer):
writer.write(reader.read())
|
python2.7后nested就过时了:
1
2
|
with
open
(
'fileToRead.txt'
,
'r'
) as reader,
open
(
'fileToWrite.txt'
,
'w'
) as writer:
writer.write(reader.read())
|
closing(object):创建上下文管理器,在执行过程离开with语句时自动执行object.close():
1
2
3
4
5
6
7
|
class
Door(
object
) :
def
open
(
self
) :
print
'Door is opened'
def
close(
self
) :
print
'Door is closed'
with contextlib.closing(Door()) as door :
door.
open
()
|