with 语句是一种与异常处理相关的功能。with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源。
with语句的语法格式如下:
with context_expression [as target(s)]:
with-body
在介绍with语句的工作原理之前,先来了解下上下文管理器。有了上下文管理器,with语句才能工作。
几个术语:
上下文管理协议(Context Management Protocol):支持该协议的对象要实现 __enter__() 和 __exit__()这两个方法。
上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__() 和 __exit__() 方法。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。
运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 __enter__() 和__exit__() 方法实现,__enter__() 方法在语句体执行之前进入运行时上下文,__exit__() 在语句体执行完后从运行时上下文退出。
上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象。如果指定了 as 子句的话,会将上下文管理器的 __enter__() 方法的返回值赋值给 target(s)。target(s) 可以是单个变量,或者由“()”括起来的元组(不能是仅仅由“,”分隔的变量列表,必须加“()”)。
语句体(with-body):with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的 __enter__() 方法,执行完语句体之后会执行 __exit__() 方法。
Python 的一些内建对象支持上下文管理器,可以用于 with 语句中,比如可以自动关闭文件、线程锁的自动获取和释放等。
with语句可以用try/finally语句替换,如下:
with语句:
with open(r'somefileName') as somefile:
for line in somefile:
print line
try/finally语句:
somefile = open(r'somefileName')
try:
for line in somefile:
print line
finally:
somefile.close()
上下文管理器可以在开发中自定义,只需实现__enter__()和__exit__()两个方法。
为了对已有的生成器函数或者对象进行包装,加入对上下文管理协议的支持,避免了专门编写上下文管理器来支持 with 语句,可以用python模块contextlib提供的装饰器 contextmanager、函数 nested 和上下文管理器 closing来实现。
装饰器(contextmanager)
contextmanager 用于对生成器函数进行装饰,生成器函数被装饰以后,返回的是一个上下文管理器,其 __enter__() 和 __exit__() 方法由 contextmanager 负责提供。
语法:
@contextmanager
def some_generator():
try:
yield
finally:
with some_generator() as :
from contextlib import contextmanager
@contextmanager
def demo():
print '[Allocate resources]'
print 'Code before yield-statement executes in __enter__'
yield '*** contextmanager demo ***'
print 'Code after yield-statement executes in __exit__'
print '[Free resources]'
with demo() as value:
print 'Assigned Value: %s' % value
输出:
[Allocate resources]
Code before yield-statement executes in __enter__
Assigned Value: *** contextmanager demo ***
Code after yield-statement executes in __exit__
[Free resources]
nested 可以将多个上下文管理器组织在一起,避免使用嵌套 with 语句。
语法:
with nested(A(), B(), C()) as (X, Y, Z):
# with-body code here
@contextmanager
def make_context(name):
print 'entering:', name
yield name
print 'exiting:', name
with make_context('AA') as A, make_context('BB') as B, make_context('CC') as C:#同nested的用法
print 'inside with statement:', A, B, C
with nested(make_context('AA'), make_context('BB'), make_context('CC')) as (A,B,C):
print 'inside with statement:', A, B, C
输出:
entering: AA
entering: BB
entering: CC
inside with statement: AA BB CC
exiting: CC
exiting: BB
exiting: AA
entering: AA
entering: BB
entering: CC
inside with statement: AA BB CC
exiting: CC
exiting: BB
exiting: AA
closing 上下文管理器包装起来的对象必须提供 close() 方法的定义,否则执行时会报 AttributeError 错误。closing 适用于提供了 close() 实现的对象,比如网络连接、数据库连接等,也可以在自定义类时通过接口 close() 来执行所需要的资源“清理”工作。
语法:
with closing(.open()) as f:
from contextlib import closing,nested,contextmanager
class ClosingDemo(object):
def __init__(self):
self.acquire()
def acquire(self):
print 'Acquire resources.'
def free(self):
print 'Clean up any resources acquired.'
def close(self):
self.free()
with closing(ClosingDemo()):
print 'Using resources'
输出:
Acquire resources.
Using resources
Clean up any resources acquired.