关于with和contextmanager
whith的使用
术语
要使用 with 语句,首先要明白上下文管理器这一概念。
有了上下文管理器,with 语句才能工作。
下面是一组与上下文管理器和with 语句有关的概念。
上下文管理协议(Context Management Protocol):
包含方法
__enter__()
和__exit__()
,支持该协议的对象要实现这两个方法。上下文管理器(Context Manager):
支持上下文管理协议的对象,这种对象实现了
__enter__()
和__exit__()
方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。运行时上下文(runtime context):
由上下文管理器创建,通过上下文管理器的
__enter__()
和__exit__()
方法实现,__enter__()
方法在语句体执行之前进入运行时上下文,__exit__()
在语句体执行完后从运行时上下文退出。with 语句支持运行时上下文这一概念。上下文表达式(Context Expression):
with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象。
语句体(with-body):
with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的
__enter__()
方法,执行完语句体之后会执行__exit__()
方法。通俗解释
一个类只要写有
__enter__()
和__exit__()
方法,就说明支持运行时上下文,使用whit 可以调用它。使用场景
有一些任务,可能事先需要设置,事后做清理工作,这时就可以使用with了。
执行流程是:
- 先调用
__enter__()
方法 - 执行whith中的内容
- 调用
__exit__()
方法
- 先调用
with基本语法和工作原理
下面使用读取文件作为示例
不用with语句
123file = open("/tmp/foo.txt")data = file.read()file.close()这里有两个问题:
(1) 是可能忘记调用close关闭文件句柄;
(2) 是文件读取数据发生异常,没有进行任何处理。
使用try … finally file.read()无论结果如何 都回执行finally
12345file = open("/tmp/foo.txt")try:data = file.read()finally:file.close()使用with的版本
12with open("/tmp/foo.txt") as file:data = file.read()除了有更优雅的语法,with还可以很好的处理上下文环境产生的异常。
如下说明了 with的执行流程
123456789101112"x.txt")f = open(f<open file 'x.txt', mode 'r' at 0x00AE82F0>f.__enter__()<open file 'x.txt', mode 'r' at 0x00AE82F0>1)f.read('X'None, None, None)f.__exit__(1)f.read(Traceback (most recent call last):File "<stdin>", line 1, in <module>ValueError: I/O operation on closed file
contextmanager的使用
创建上下文管理
123456789101112131415161718192021class Context(object):def __init__(self, handle_error):print '__init__(%s)' % handle_errorself.handle_error = handle_errordef __enter__(self):print '__enter__()'return selfdef __exit__(self, exc_type, exc_val, exc_tb):print '__exit__(%s, %s, %s)' % (exc_type, exc_val, exc_tb)return self.handle_errorwith Context(True):raise RuntimeError('error message handled')printwith Context(False):raise RuntimeError('error message propagated')执行结果
12345678910111213$ python contextlib_api_error.py__init__(True)__enter__()__exit__(<type 'exceptions.RuntimeError'>, error message handled, <traceback object at 0x10046a5f0>)__init__(False)__enter__()__exit__(<type 'exceptions.RuntimeError'>, error message propagated, <traceback object at 0x10046a680>)Traceback (most recent call last):File "contextlib_api_error.py", line 30, in <module>raise RuntimeError('error message propagated')RuntimeError: error message propagated上面列出了传统的创建上下文的方法,前提是你要先定义类,并且要提前定义
__enter__()
和__exit__()
方法,比较麻烦。python 官方提供了contextlib模块作为上下文管理工具
小例子看下它怎么用1234567891011121314from contextlib import contextmanager@contextmanagerdef tag(name):print("<%s>" % name)yieldprint("</%s>" % name)>>> with tag("h1"):... print("foo")...<h1>foo</h1>如下示例:打开一个文件,确保文件关闭
123456789101112from contextlib import contextmanagerdef opened(filename, mode="r"):f = open(filename, mode)try:yield ffinally:f.close()按照如下方法使用:with opened("/etc/passwd") as f:for line in f:print line.rstrip()如下示例: 提交或回滚一个数据库
12345678910def transaction(db):db.begin()try:yield Noneexcept:db.rollback()raiseelse:db.commit()contextlib.closing(thing)的使用
closing的作用其实就是,最后调用类中提前定义的close方法
closing的基本实现如下:12345678from contextlib import contextmanagerdef closing(thing):try:yield thingfinally:thing.close()使用方法如下
123456from contextlib import closingfrom urllib.request import urlopenwith closing(urlopen('http://www.python.org')) as page:for line in page:print(line)Python 3.2 中新增 contextlib.ContextDecorator,
可以允许我们自己在 class 层面定义新的”上下文管理修饰器“。12345678910111213141516171819202122232425262728293031323334353637from contextlib import ContextDecoratorclass mycontext(ContextDecorator):def __enter__(self):print('Starting')return selfdef __exit__(self, *exc):print('Finishing')return False@mycontext()def function():'The bit in the middle')print(...function()StartingThe bit in the middleFinishingwith mycontext():'The bit in the middle')print(...StartingThe bit in the middleFinishingThis change is just syntactic sugar for any construct of the following form:def f():with cm():# Do stuffContextDecorator lets you instead write:def f():# Do stuff参考:
https://pymotw.com/2/contextlib/