with
的作用
with
语法在Python里很常见, 主要的利好是使用代码更简洁. 常见的使用场景有:
1. 资源对象的获取与释放. 使用with
可以简化try...finally ...
2. 在不修改函数代码的前提下设置函数的默认参数
3. …
例如, 读写一个文件. 在读写前, 要打开它;在读写结束后要关闭它;读写过程中出现异常也得关闭它.
如果不使用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作出的修改恢复成原样.
使用contextlib.contextmanager
简化CM实现
Python提供了标准的修饰器: contextlib.contextmanager
用于简化CM的实现:
import contextlib
@contextlib.contextmanager
def reverse_print():
# 这个方法在进入with block时执行.
import sys
origin_write = sys.stdout.write
print('enter with block.')
def new_print(s):
origin_write(s[::-1])
sys.stdout.write = new_print# Python2里这个对象不可修改, Python3可以
yield 'HELLO, I AM WITH'
# 跳出with block时执行.
# 处理异常(这儿就忽略吧), 恢复进入with时的修改
sys.stdout.write = origin_write
print('exit with block')
用生成器函数重写后的CM, 调用方式与调用效果和用class实现时一模一样.
大概原理:
1. 通过修饰器将生成器函数封装到一个CMO class里
2. yield
之前的代码在__enter__
里执行, 之后的代码在__exit__
里执行. yield
本身的作用相当于__enter__
里的return
.