with语法在Python里很常见, 主要的利好是使用代码更简洁. 常见的使用场景有:
1. 资源对象的获取与释放. 使用with可以简化try...finally ...
2. 在不修改函数代码的前提下设置函数的默认参数
例如, 读写一个文件. 在读写前, 要打开它;在读写结束后要关闭它;读写过程中出现异常也得关闭它.
如果不使用with, 就得这么写:
try: f = open('xxx', 'w') # write ... finally: f.close()
如果使用with, 则简短不少:
with open('xxx', 'r') as f: # write ...
一个具体的示例可以看出with语法的特点:
# encoding=utf-8 path = '/tmp/temp.txt' with open(path, 'w') as f: f.writelines(['hello']) print '在with之外, 变量f仍然存在且可被访问:', f print '但是, 文件流已经被关闭了: ', f.closed
输出:
在with之外, 变量f仍然存在且可被访问: <closed file '/tmp/temp.txt', mode 'w' at 0x7ff7f4076660> 但是, 文件流已经被关闭了: True
with语法在tensorflow/slim里被大量使用, 主要用于管理命名和设置默认参数:
with slim.arg_scope( [slim.conv2d], activation_fn=tf.nn.relu, weights_initializer= weights_initializer, biases_initializer = biases_initializer): with slim.arg_scope( [slim.conv2d, slim.max_pool2d], padding='SAME', data_format = self.data_format): ...
上下文管理对象
为了for... in ..., Iterator出现了. 为了with...[as ...], 上下文管理对象(Context Manager, CM)出现了.
with产生了类似于作用域的效果, 例如, 用with ... as 打开的文件流, 只能在with 代码块内部进行I/O操作;使用slim.arg_scope为目标函数设置了默认参数, 但这个设置只在with 代码块内部生效. 出了with 代码块, 该怎样还是怎样. 这是怎样实现的呢? 答案在上下文管理器(CM)身上.
CM是一种协议, 它的协议方法为__enter__与__exit__, 前者在进入with代码块时执行, 后者在退出时执行.
实现一个简单的上下文管理器
接下来我们通过实现一个简单的CM来理解with的工作原理, 它会将print输出反转:
# encoding=utf-8 # !/usr/local/bin/Python3 class reverse_print(): def __enter__(self): # 这个方法在进入with block时执行. print('enter with block.') import sys self.origin_write = sys.stdout.write sys.stdout.write = self.new_print# Python2里这个对象不可修改, Python3可以 return 'HELLO, I AM WITH'# 被 as 接收 def new_print(self, s): self.origin_write(s[::-1]) def __exit__(self, exception_type, exception_code, traceback): # 跳出with block时执行. # 处理异常(这儿就忽略吧), 恢复进入with时的修改 import sys sys.stdout.write = self.origin_write print('exit with block') print('before with:', 'hello') with reverse_print() as cmo: print('hello') print(cmo) print('after with:','hello') print(cmo)
用Python3执行, 输出为:
before with: hello enter with block. olleh HTIW MA I ,OLLEH exit with block after with: hello HELLO, I AM WITH
这段代码展示了CMO的两个关键点:
-
__enter__
在进入with
时执行. 它的返回值会被as
接收. -
__exit__
在退出with
时执行, 需要将在__enter__
对context作出的修改恢复成原样.