Python中with/as语句的深入解析:原理、用法与自定义实现
本文深入解析Python中with/as语句的工作原理,阐述其作为try/finally替代方案的优势。详细讲解环境管理协议、异常处理机制及多环境管理器用法,并提供自定义环境管理器的完整代码示例,帮助开发者安全高效地管理文件、锁等资源。
with/as
对于使用open()函数打开文件的开发者来说,with/as语句并不陌生。实际上,with/as提供了一种比try/finally更简洁的资源管理替代方案。
当某个对象实现了被称为“环境管理协议”的接口时,系统会通过环境管理器自动执行必要的清理工作。这类似于finally块的功能:无论代码执行过程中是否发生异常,最终都会执行预设的清理操作。
用法:
with expression [as var]: with_block_code
当expression表达式返回的对象支持环境管理协议时,即可使用with语句。as var部分是可选的;如果省略as var,expression返回的对象将被丢弃;如果指定了as var,则将该返回对象赋值给变量var。
整体流程大致如下:首先评估expression表达式,若其返回对象支持环境管理协议,则进入with/as语句块结构;在准备退出with语句块时,系统将执行该对象中定义的清理操作。关于工作机制的具体细节,下文将详细说明。
例如,open()函数返回的文件对象支持环境管理协议,因此可以使用with/as来安全地打开文件:
with open(r'd:\a\b\c\a.log') as logfile: for line in logfile: print(line) ...more code here...
整个过程是先调用open(),接着进入with/as语句块。在逐行输出文件内容后,当准备退出with语句块时,环境管理器会根据文件对象中定义的操作自动关闭文件。
它实际上等价于以下代码结构:
myfile = open(r'd:\a\b\c\a.log') try: for line in myfile: print(line) ...more code here... finally: myfile.close()
虽然文件对象在不再被引用时,垃圾回收器会自动回收该对象,但垃圾回收器的回收操作存在延迟。换句话说,如果不使用with/as打开文件,也不显式调用close()关闭文件,该文件在用完之后可能会保持占用状态一段时间,直到垃圾回收器执行回收操作。
with/as不仅用于文件的打开与关闭,锁操作也支持环境管理协议,这意味着在需要时会自动释放锁资源。
嵌套多个环境管理器
在Python 3.1及之后的版本中,with/as支持同时管理多个环境管理器,只需使用逗号分隔即可。
with A() as a, B() as b: ...statements...
它等价于嵌套的with语句:
with A() as a: with B() as b: ...statements...
由多环境管理器管理的多个对象,在with语句块中出现异常时,或者执行完with语句块后,会自动全部被清理(例如执行文件关闭操作)。
例如,同时打开两个文件,通过zip()函数合并它们的内容,并在语句块结束时同时关闭这两个文件:
with open('a.file') as f1, open('b.file') as f2:
for pair in zi[(f1, f2):
print(pair)
自定义环境管理器
无论是文件对象还是锁对象,都是他人已实现环境管理器的类。我们也可以自己编写环境管理器,使其支持with/as语法,这实际上属于运算符重载的范畴。
要编写自己的环境管理器,首先需要了解with/as工作机制的细节:
- 首先评估expression表达式,其返回结果是一个对象。该对象必须具有
__enter__和__exit__方法,这样的返回对象被称为“环境管理器”。 - 接着调用环境管理器的
__enter__方法。__enter__方法的返回值会被赋值给as指定的变量,或者在没有使用as时被直接丢弃。 - 随后执行with语句块中的代码内容。
- 如果在执行with语句块中的代码时抛出异常,系统将调用
__exit__(type,value,traceback)方法。其中type、value和traceback这三个与异常相关的参数来源于sys.exc_info。如果__exit__方法返回值为False,则异常会被自动重新抛出以继续传播;如果返回值为True(或等效真值),则异常被认为已被合理处理。 - 如果with语句块中的代码没有抛出异常,则直接调用
__exit__(None,None,None),即向该方法传递的三个参数均为None值。
看一个简单的示例:
class TraceBlock:
def message(self, arg):
print('running ' + arg)
def __enter__(self):
print('starting with block')
return self
def __exit__(self, exc_type, exc_value, exc_tb):
if exc_type is None:
print('exited normally\n')
else:
print('raise an exception! ' + str(exc_type))
return False
上面的 __enter__ 方法返回的对象会赋值给as关键字指定的变量,在这个示例中即返回对象自身。如果有需求,也可以返回其他对象。
在上面的__exit__ 方法中,如果异常类型(exc_type)为None,说明with语句块中的语句执行过程没有抛出异常,程序正常结束即可。但如果存在异常,则要求该方法返回False。实际上,上面的 return False 语句可以省略,因为函数没有显式return时默认返回None,而None的布尔值代表False。
测试下:
with TraceBlock() as action:
action.message("test 1")
print("reached")
print('-' * 20, "\n")
with TraceBlock() as action:
action.message("test 2")
raise TypeError
print("not reached")
结果如下:
starting with block running test 1 reached exited normally
--------------------
starting with block running test 2 raise an exception! <class 'TypeError'> Traceback (most recent call last): File "g:/pycode/list.py", line 23, in <module> raise TypeError TypeError
定义环境管理器并非易事。一般来说,如果需求不是很复杂,直接使用try/finally来定义相关操作即可。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









