with
语句是从 Python 2.5 开始引入的一种与异常处理相关的功能(2.5 版本中要通过 from __future__ import with_statement
导入后才可以使用),
从 2.6 版本开始缺省可用。
with
语句适用于对资源进行访问的场合,
确保不管使用过程中是否发生异常都会执行必要的“清理”操作,
释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
推荐使用 with
语句操作文件 IO。
如果文件较大,可以按字节读取或按行读取。
使用文件迭代器进行逐行迭代。
术语
要使用 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__()
方法。
with
语句
语法格式如下:
with context_expression [as target(s)]:
with-body
这里 context_expression
要返回一个上下文管理器对象,该对象并不赋值给 as 子句中的 target(s)
,
如果指定了 as
子句的话,会将上下文管理器的 __enter__()
方法的返回值赋值给 target(s)
。
target(s)
可以是单个变量,或者由“()”括起来的元组(不能是仅仅由“,”分隔的变量列表,必须加“()”)。
Python 对一些内建对象进行改进,加入了对上下文管理器的支持,可以用于 with
语句中,
比如可以自动关闭文件、线程锁的自动获取和释放等。假设要对一个文件进行操作,
使用 with
语句可以有如下代码:
with
语句操作文件对象
with open(r'xx_1.txt') as somefile:
for line in somefile:
print (line)
somefile = open(r'xx_1.txt')
try:
for line in somefile:
print (line)
# ...more code
finally:
somefile.close()
class MyContextManager:
def __init__(self):
print ("MyContextManager initialized" )
def __enter__(self):
print ("Entered MyContextManager" )
def __exit__(self, *exc):
print ("Exited MyContextManager" )
__exit__
方法需要有三个参数,这里为了简化,将它笼统定义为*exc
。后面的博文会对这些参数做更详细的说明。
下面的with
语句利用了刚刚定义的context manager
类。
with MyContextManager():
print ("In main block" )
MyContextManager initialized Entered MyContextManager In main block Exited MyContextManager
由此,可以大概了解with语句的执行过程:
评估
with
语句中的context
表达式,并创建context manager
对象。(事实上,一个with
语句中可以有多个context manager
对象,后文再介绍。)加载
context manager
的__exit__
方法,以备后用。执行
context manager
的__enter__
方法。执行
with
语句块。执行
context manager
的__exit__
方法。
f = open ("xx_test1.txt" ,'w')
f.write ("test")
4
!cat xx_test1.txt
答案是:当前目录下生成了一个文件 test1.txt
,并且在里面写人了字符串 test
。
然而事实真相是:的确生成了一个文件,但其内容为空,并没有写人任何字符串。
这个一个简单得不能再简单的问题,相信不用多说已经知道症结所在了。
对文件操作完成后应该立即关闭它们,这是一个常识,都知道需要这么做,
在很多编程语言中都会强调这个问题,因为打开的文件不仅会占用系统资源,
而且可能影响其他程序或者进程的操作,甚至会导致用户期望与实际操作结果不一致。
但实际应用中真相往往是:即使心中记得这个原则,但仍然可能会忘记关闭它。
为什么?因为编程人员会把更多的精力和注意力放在对具体文件内容的操作和处理上;
或者设计的正常流程是处理完毕关闭文件,但结果程序执行过程中发生了异常导致关闭文件的代码没有被执行到。
也许会说,还有 try finally
块。对!这是一种比较古老的方法,但Python提供了一种更为简单的解决方案: with
语句。
with
语句的语法为:
with 表达式 [as 目标]:
代码块
with
语句支持嵌套,支持多个 with
子句,它们两者可以相互转换。 with exprl as el
,expr2 as e2
与下面的嵌套形式等价:
with exprl as el:
with expr2 as e2:
with
语句的使用非常简单,本节开头的例子改用 with
语句能够保证当写操作执行完毕后自动关闭文件。
with open ('xx_test.txt','w') as f:
f.write ("test")
with
语句可以在代码块执行完毕后还原进入该代码块时的现场,包含有 with
语句的代 码块的执行过程如下:
- 计算表达式的值,返回一个上下文管理器对象。
- 加载上下文管理器对象的
__exit__()
方法以备后用。 - 调用上下文管理器对象的
__enter__()
方法。 - 如果
with
语句中设置了目标对象,则将__enter__()
方法的返冋值赋值给目标对象。 - 执行
with
中的代码块。 - 如果步骤 5 中代码正常结束,调用上下文管理器对象的
__exit__()
方法,其返回值直 接忽略。 - 如果步骤5中代码执行过程中发生异常,调用上下文管理器对象的
__exit__()
方法, 并将异常类型、值及traceback
信息作为参数传递给__exit__()
方法。 如果返回值为False
, 则异常会被重新抛出;如果其返回值为True
,异常被挂起,程序继续执行。
在文件处理时使用 with
的好处在于无论程序以何种方式跳出 with
块,总能保证文件被正确关闭。
实际上它不仅仅针对文件处理,针对其他情景同样可以实现运行时环境的清理与还原,如多线程编程中的锁对象的管理。
with
的神奇实际得益于一个称为上下文管理器 (context manager)的东西:它用来创建一个运行时的环境。
上下文管理器是这样一个对象: 它定义程序运行时需要建立的上下文,处理程序的进入和退出,实现了上下文管理协议,
即在对象中定义 __enter__()
和 __exit__()
方法。其中:
__enter__()
进入运行时的上下文,返回运行时上下文相关的对象,with
语句中会将 这个返回值绑定到目标对象。如上面的例子中会将文件对象本身返回并绑定到目标f
。__exit__(cxception_typc,exception_va]ue,traceback)
: 退出运行时的上下文,定义在块执行 <或终止)之后上下文管理器应该做什么。它可以处理异常、清理现场或者处理with
块中语句执行完成之后需要处理的动作。
实际上任何实现了上下文协议的对象都可以称为一个上下文管理器,
文件也是实现了这个协议的上下文管理器,它们都能够与 with
语句兼容。文件对象的 __enter__
和 __exit__
属性如下:
f.__enter__
<function TextIOWrapper.__enter__>
f.__exit__
<function TextIOWrapper.__exit__>
用户也可以定义自己的上下文管理器来控制程序的运行,只需要实现上下文协议便能够和 with
语句一起使用。
class MyContextManager (object):
def __enter__(self):
# 实现_enter_方法 ...
print ("entering.")
def __exit__(self ,exception_type, exception_value, traceback):
print ("leaving...")
if exception_type is None:
print ("no exceptions!")
return False
elif exception_type is ValueError:
print ("value error!!!")
return True
else:
print ("other error")
return True
with MyContextManager () :
print ("Testing...")
raise(ValueError)
entering. Testing... leaving... value error!!!
因为上下文管理器主要作用于资源共享,因此在实际应用中 __enter__()
和 __exit__()
方法基本用于资源分配以及释放相关的工作,
如打开/关闭文件、异常处理、断开流的连接、 锁分配等。
为了更好地辅助上下文管理,Python还提供了 contextlib
模块,该模块是通过 Generator
实现的,
coatextlib
中的 contextmanager
作为装饰器来提供一种针对函数级別的上下文管理机制,
可以直接作用于函数/对象而不用去关心 __enter__()
和 __exit__()
方法的具体实现。