上下文管理器要负责一个代码块中的资源,可能在进入代码块时创建资源,然后在退出代码块时清理这个资源。
文件支持上下文管理器API,可以很容易地确保完成文件读写后关闭文件
>>> with open('/tmp/file.txt','wt') as f:
... f.write('continue to goa here')
...
上下文管理器由with语句启用,这个API包括两个方法。当执行流进入with中的代码块时会运行__enter__()方法。它会返回一个对象,在这个上下文中使用。当执行流离开with块时,则调用这个上下文管理器的__exit__()方法来清理所使用的资源。
>>> class Context(object):
... def __init__(self):
... print '__init__()'
... def __enter__(self):
... print '__enter__'
... return self
... def __exit__(self,exc_type, exc_val, exc_tb):
... print '__exit__'
...
>>> with Context():
... print 'Doing work in the context'
...
__init__()
__enter__
Doing work in the context
__exit__
>>>
使用as创建with语句中__enter__()返回的对象别名
>>> class WithinContext(object):
... def __init__(self, context):
... print 'WithinContext.__init__(%s)' % context
... def do_something(self):
... print 'WithinContext.do_something()'
... def __del__(self):
... print 'WithinContext.__del__'
...
>>> class Context(object):
... def __init__(self):
... print 'Context.__init__()'
... def __enter__(self):
... print 'Context.__enter__()'
... return WithinContext(self)
... def __exit__(self, exc_type, exc_val, exc_tb):
... print 'Context.__exit__()'
...
>>> with Context() as c:
... c.do_something()
...
Context.__init__()
Context.__enter__()
WithinContext.__init__(<__main__.Context object at 0xb74a2f6c>)
WithinContext.do_something()
Context.__exit__()
>>>
与变量c关联的值是__enter__()返回的对象,这不一定是with语句中创建的Context实例。
__exit__()方法接受一些参数,其中包含with块中产生的异常的详细信息。
>>> class Context(object):
... def __init__(self, handle_error):
... print '__init__(%s)' % handle_error
... self.handle_error = handle_error
... def __enter__(self):
... print '__enter__()'
... return self
... def __exit__(self, exc_type, exc_val, exc_tb):
... print '__exit__()'
... print ' exc_type = ', exc_type
... print ' exc_val = ', exc_val
... print ' exc_tb = ', exc_tb
... return self.handle_error
...
>>> with Context(True):
... raise RuntimeError('error message handled')
...
__init__(True)
__enter__()
__exit__()
exc_type = <type 'exceptions.RuntimeError'>
exc_val = error message handled
exc_tb = <traceback object at 0xb74a334c>
>>>
>>> with Context(False):
... raise RuntimeError('error message propagated')
...
__init__(False)
__enter__()
__exit__()
exc_type = <type 'exceptions.RuntimeError'>
exc_val = error message propagated
exc_tb = <traceback object at 0xb74ce20c>
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
RuntimeError: error message propagated
>>>
如果上下文可以处理这个异常,__exit__() 应当返回一个true值来指示不需要传播这个异常,如果返回false,就会导致__exit__()返回后重新抛出这个异常。
从生成器到上下文管理器
使用contextmanager()修饰符将一个生成器函数转换成上下文管理器。
1 #! /usr/bin/python
2 # -*- coding:utf-8 -*-
3
4 import contextlib
5
6 @contextlib.contextmanager
7 def make_context():
8 print ' entering'
9 try:
10 yield {}
11 except RuntimeError, err:
12 print ' ERROR:', err
13 finally:
14 print ' exiting'
15 print 'Normal:'
16 with make_context() as value:
17 print ' inside with statement:', value
18
19 print '\nHandled error'
20 with make_context() as value:
21 raise RuntimeError('showing example of handling an error')
22
23 print '\nUnhandled error:'
24 with make_context() as value:
25 raise ValueError('this exception is not handled')
输出结果:
Normal:
entering
inside with statement: {}
exiting
Handled error
entering
ERROR: showing example of handling an error
exiting
Unhandled error:
entering
exiting
Traceback (most recent call last):
File "contextlib_contextmanager.py", line 25, in <module>
raise ValueError('this exception is not handled')
ValueError: this exception is not handled
生成器要初始化上下文,用yield生成一次值,然后清理上下文。所生成的值(如果有)会绑定到with语句as子句中的变量。with块中的异常会在生成器中重新抛出,使之在生成器中得到处理。
嵌套上下文
1 #! /usr/bin/python
2 # -*- coding:utf-8 -*-
3
4 import contextlib
5 @contextlib.contextmanager
6 def make_context(name):
7 print ' entering:', name
8 yield name
9 print 'exiting:', name
10
11 with make_context('A') as A, make_context('B') as B:
12 print 'inside with statement:', A,B
输出结果
entering: A
entering: B
inside with statement: A B
exiting: B
exiting: A
程序执行时会按其进入上下文的逆序离开上下文。每个上下文管理器与as子句(可选)之间用一个逗号(,)分隔。
关闭打开的句柄
file类直接支持上下文管理器API,不过表示打开句柄的另外一些对象并不支持这个API。contextlib的标准库文档给出的例子是一个由urllib.urlopen()返回的对象。还有一些遗留类,他们使用close()方法而不支持上下文管理器API。为了确保关闭句柄,需要使用closing()为它创建一个上下文管理器。
1 #! /usr/bin/python
2 # -*- coding:utf-8 -*-
3
4 import contextlib
5 class Door(object):
6 def __init__(self):
7 print ' __init__()'
8 def close(self):
9 print ' close()'
10
11
12 print 'Normal Example:'
13 with contextlib.closing(Door()) as door:
14 print ' inside with statement'
15
16 print '\nError handling example:'
17 try:
18 with contextlib.closing(Door()) as door:
19 print ' raiseing from inside with statement'
20 raise RuntimeError('error message')
21 except Exception, err:
22 print ' Had an error:', err
输出结果:
Normal Example:
__init__()
inside with statement
close()
Error handling example:
__init__()
raiseing from inside with statement
close()
Had an error: error message
不论with块中是否有一个错误,这个句柄都会关闭。