以下 3 点尤其第一条我觉得十分重要 \^_^
1. 使你的 python 代码看起来更 pythonic
2. 简化了 try/finally 模式,减少了模板代码的数量
3. 代码安全性更高,也更易于使用
既然说到了 with 是简化 try/finally 模式,我们就先看一下简化之前的样子
#! /usr/bin/env python
# -*- coding: utf-8 -*-
def create_file_by_try(file):
try:
f = open(file, 'w')
f.write("A create file demo")
except Exception as e:
print(e.with_traceback())
finally:
f.close()
if __name__ == "__main__":
create_file_by_try('demo.txt')
假如你是来自 Java、C++ 的开发者,估计已经感觉这段代码足够简洁了,但如果按照 The zen of python 的宗旨来看,这段代码还远远不够简洁,同时这段代码也存在安全风险,例如:如果传入的参数不是 demo.txt 而是一个 空的地址,虽然 try 防护住了 f = open(file, ‘w’),f.write(“A create file demo”) 这两行代码,但是 finally 块中的 f.close(),还是会因为 f 没有被赋值,导致抛出异常,那么我们为了代码的健壮性是否还要在 finally 块中添加 try/except 呢?再看如下这段稍微健壮些的代码是否还觉得足够简洁了呢?
#! /usr/bin/env python
# -*- coding: utf-8 -*-
def create_file_by_try2(file):
try:
f = open(file, 'w')
f.write("A create file demo.")
except Exception as e:
print(e)
finally:
try:
f.close()
except Exception as e :
print(e)
if __name__ == "__main__":
create_file_by_try2('')
#! /usr/bin/env python
# -*- coding: utf-8 -*-
def create_file_by_with(file):
with open(file, 'w') as f:
f.write("A new create file demo.")
if __name__ == "__main__":
create_file_by_with('demo.txt')
观察两种实现方式就会发现使用 with 语句模板代码少了有 2/3 ,同时不在需要手工的进行 close 等事后的清理操作,所以不但代码简洁了许多,同时代码风险也下降了。
上面只对 with 进行了简单使用,并没有阐述它的原理,我们如果要更广泛的使用 with 就必须了解它的原理。说到 with 与之关联的就是上下文管理器,那么什么是上下文管理器呢?
上下文管理器就是实现了上下文管理器协议的对象就被称为上下文管理器。这里又牵扯出了上下文管理器协议。
上下文管理器协议: 包含 __enter__ ,__exit__ 两个方法。所以简单解释如果一个对象实现了 __enter__ ,__exit__ 两个方法,那么这个类就是一个上下文管理器。
with 语句开始运行时会创建一个临时的上下文,交给上下文管理器控制,同时先调用上下文管理器的 __enter__ 方法,__enter__ 有一个返回值,这个值会被赋值给 as 后面的对象,例如:with open(file, ‘w’) as f 中的 f 对象。注意此处赋值给 f 的并不是 open 函数的返回值,而是 __enter__ 的返回值,如果 __enter__ 的返回值为空,with 语句可以省略 as 语句。with 语句结束时会调用 __exit__ 方法,这个方法的作用主要进行清理工作。
自定义一个上下文管理器my_context.py:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
class MyContext(object):
def __enter__(self):
print('run __enter__')
return "我就是 MyContext.__enter__ 的返回值"
'''
:exc_type 异常的类型
:exc_val 异常的值内容
:exc_tb 异常的调用链路信息
'''
def __exit__(self, exc_type, exc_val, exc_tb):
print('run __exit__')
# 主要进行清理操作,如果返回值为 None 或者非 True,with 语句中的异常将会向上一层抛出, 注意是 with 语句的异常
return True
测试代码 main.py:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from my_context import MyContext
def test():
with MyContext() as name:
# raise Exception
print(name) # 此处打印的是 \_\_enter__的返回值
if __name__ == "__main__":
test()