TL;DR 上下文管理器通过控制代码块级别的上下文,可以实现的很多诸如自动关闭文件、捕获异常等功能
什么是上下文管理器
上下文管理器 context manager
能够控制程序执行的上下文,比如控制文件的关闭,抑制异常,捕获异常,修改上下文变量等
调用过程
简单例子
class Context(object):
def __enter__(self):
print("__enter__ invoked")
def __exit__(self, exc_type, exc_val, exc_tb):
print("__exit__ invoked")
with Context():
print("with block")
`</pre>
运行以上代码,则会有如下输出:
<pre>`__enter__ invoked
with block
__exit__ invoked
`</pre>
### 中级复杂的例子
<pre>`class ContextInstance(object):
def __init__(self, msg):
self.msg = msg
def say(self):
print(self.msg)
def when_exit(self):
print("instance exited!")
class Context(object):
def __init__(self, msg):
self.msg = msg
self.instance = None
super(Context, self).__init__()
def __enter__(self):
print("__enter__ invoked")
self.instance = ContextInstance(self.msg)
return self.instance
def __exit__(self, exc_type, exc_val, exc_tb):
print("__exit__ invoked")
self.instance.when_exit()
with Context("Message") as ctx:
ctx.say()
print("within block")
`</pre>
运行以上代码,则会有如下输出:
<pre>`__enter__ invoked
Message
within block
__exit__ invoked
instance exited!
`</pre>
### 复杂例子
<pre>`class ContextManager(object):
def __init__(self, msg):
self.msg = msg
self.instance = None
super(ContextManager, self).__init__()
def __enter__(self):
print("__enter__ invoked")
self.instance = ContextInstance(self.msg)
return self.instance
def __exit__(self, exc_type, exc_val, exc_tb):
print("__exit__ invoked")
self.instance.when_exit()
class ContextInstance(object):
def __init__(self, msg):
self.msg = msg
super(ContextInstance, self).__init__()
def say(self):
print(self.msg)
def when_exit(self):
print("instance existed!")
with ContextManager("Message") as ctx:
ctx.say()
print("with block")
`</pre>
运行以上代码,则会有如下输出:
<pre>`__enter__ invoked
Message
with block
__exit__ invoked
instance existed!
`</pre>
## 使用场景
### 控制文件关闭
<pre>`with open("/tmp/context_manager.txt", 'wt') as f:
f.write("contexts go here")
`</pre>
### 抑制异常
<pre>`class Context(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# supress all the exception
return True
with Context():
print("start")
raise ValueError("E!")
print("end")
print("next")
`</pre>
运行以上代码,则会有如下输出:
<pre>`start
next
`</pre>
### 捕获异常
<pre>`# TODO: see unittest catch exceptioon
`</pre>
### 修改上下文
<pre>`env_context = []
class Context(object):
def __enter__(self):
env_context.append(1)
def __exit__(self, exc_type, exc_val, exc_tb):
env_context.pop()
print("before with", len(env_context))
with Context():
print("in with", len(env_context))
print("after with", len(env_context))
`</pre>
运行以上代码,则会有如下输出:
<pre>`('before with', 0)
('in with', 1)
('after with', 0)
`</pre>
## contextlib 库
contextlib是Python官方包,使用contenxtlib可以很方便的构建上下文管理器
### contextlib.contextmanager
#### 简单用法
<pre>`import contextlib
@contextlib.contextmanager
def context():
print("before yeild")
yield []
print("after yeild")
with context() as value:
print("before value")
print(value)
print("after value")
`</pre>
运行以上代码,则会有如下输出:
<pre>`before yeild
before value
[]
after value
after yeild
`</pre>
#### 捕获异常
<pre>`import contextlib
@contextlib.contextmanager
def context():
print("before yeild")
try:
yield
except ValueError as e:
print(e)
print("after yeild")
with context():
raise ValueError("NO")
print("after exception")
print("after with")
`</pre>
运行以上代码,则会有如下输出:
<pre>`before yeild
NO
after yeild
after with
`</pre>
### 嵌套的上下文管理器 (Nesting contexts)
<pre>`import contextlib
@contextlib.contextmanager
def context(name):
print("entring %s" % name)
yield name
print("exiting %s" % name)
with contextlib.nested(context('a'), context('b'), context('c')) as (a, b, c):
print("inside with statement: %s" % ((a, b, c), ))
`</pre>
运行以上代码,则会有如下输出:
<pre>`entring a
entring b
entring c
inside with statement: ('a', 'b', 'c')
exiting c
exiting b
exiting a
/usr/local/lib/python2.7/dist-packages/ipykernel_launcher.py:9: DeprecationWarning: With-statements now directly support multiple context managers
if __name__ == '__main__':
`</pre>
观察输出,你会发现,嵌套的上下文管理器工作起来像是先入后出的栈一样:最先进入的管理器最后退出,最后进入的管理器最先退出。
同时你应该观察到一个Warning:DeprecationWarning: With-statements now directly support multiple context managers。contextlib.nested函数将于后续Python版本被弃用,Python 2.7引入了嵌套上下文管理器新语法。
#### Python 2.7 版本的新语法 (py2.7 now support nested)
<pre>`import contextlib
@contextlib.contextmanager
def context(name):
print("entring %s" % name)
yield name
print("exiting %s" % name)
with context('a') as a, context('b') as b, context('c') as c:
print("inside with statement: %s" % ((a, b, c), ))
`</pre>
运行以上代码,则会有如下输出:
<pre>`entring a
entring b
entring c
inside with statement: ('a', 'b', 'c')
exiting c
exiting b
exiting a
`</pre>
### 自动关闭的上下文管理器 (closing context)
`contextlib.closing`等价与如下代码:
<pre>`import contextlib
@contextlib.contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
`</pre>
### 简单例子
<pre>`import contextlib
class Door():
def close(self):
print("Door closing")
def do_something(self):
print("doing something")
try:
with contextlib.closing(Door()) as door:
door.do_something()
raise ValueError("Exception raising")
except ValueError as e:
print(e)
`</pre>
运行以上代码,则会有如下输出:
<pre>`doing something
Door closing
Exception raising
`</pre>
### 替代的例子
<pre>`import contextlib
@contextlib.contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
class Door():
def close(self):
print("Door closing")
def do_something(self):
print("doing something")
try:
with closing(Door()) as door:
door.do_something()
raise ValueError("Exception raising")
except ValueError as e:
print(e)
`</pre>
运行以上代码,则会有如下输出:
<pre>`doing something
Door closing
Exception raising
`</pre>
### 实际的例子
<pre>`from contextlib import closing
import urllib
fair_license_url = 'http://www.samurajdata.se/opensource/mirror/licenses/fair.txt'
with closing(urllib.urlopen(fair_license_url)) as page:
for line in page:
print(line)
`</pre>
运行以上代码,则会有如下输出:
<pre>`Fair License
<Copyright Information>
Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
[2004, Fair License: rhid.com/fair]