3.4.3 从生成器到上下文管理器
采用传统方式创建上下文管理器并不难,即编写一个包含__enter__()和__exit__()方法的类。不过有些时候,如果只有很少的上下文要管理,那么完整地写出所有代码便会成为额外的负担。在这些情况下,可以使用contextmanager()修饰符将一个生成器函数转换为上下文管理器。
import contextlib
@contextlib.contextmanager
def make_context():
print(' entering')
try:
yield {}
except RuntimeError as err:
print(' ERROR:',err)
finally:
print(' exiting')
print('Normal:')
with make_context() as value:
print(' inside with statement:',value)
print('\nHandled error:')
with make_context() as value:
raise RuntimeError('showing example of handling an error')
print('\nUnhandled error:')
with make_context() as value:
raise ValueError('this exception is not handled')
生成器要初始化上下文,调用一次yield,然后清理上下文。所生成的值(如果有)会绑定到with语句as子句中的变量。with块中抛出的异常会在生成器中再次抛出,从而可以在生成器中得到处理。
运行结果:
contextmanager()返回的上下文管理器派生自ContextDecorator,所有也可以被用作函数修饰符。
import contextlib
@contextlib.contextmanager
def make_context():
print(' entering')
try:
# Yield control,but not a value,because any value
# yieled is not available when the context manager
# is used as a decorator.
yield
except RuntimeError as err:
print(' ERROR:',err)
finally:
print(' exiting')
@make_context()
def normal():
print(' inside with statement')
@make_context()
def throw_error(err):
raise err
print('Normal:')
normal()
print('\nHandled error:')
throw_error(RuntimeError('showing example of handling an error'))
print('\nUnhandled error:')
throw_error(ValueError('this exception is not handled'))
与前面的ContextDecorator例子一样,上下文管理器被用作修饰符时,生成器生成的值在被修饰的函数中不可用。传入被修饰函数的参数仍然可用,如这个例子中的throw_error()所示。
运行结果: