本文主题,讲解with的使用与神奇。
- with语法与使用前提;
- with与异常处理;
- 上下文管理器模块contextlib;
- 使用with处理图形上下文;
一、with语法与使用前提
1. with语法
语法一
with 对象:
with语句
语法二
with 对象 as 对象别名:
with语句
2. with使用前提
先从一段运行错误的代码开始:
#coding=utf-8
a=20
with a:
print("with body")
该段代码执行错误如下:
with使用的错误情况
该错误提示:对象a的AttributeError:__enter__。
实际上,该错误发生的原因是:
with中使用的对象必须实现__enter__与__exit__函数。
备注:下面的解释引用网上的文章(https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/)。
因为with的工作机制与流程如下:
context_manager = context_expression
exit = type(context_manager).__exit__
value = type(context_manager).__enter__(context_manager)
exc = True # True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理
try:
try:
target = value # 如果使用了 as 子句
with-body # 执行 with-body
except:
# 执行过程中有异常发生
exc = False
# 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常
# 由外层代码对异常进行处理
if not exit(context_manager, *sys.exc_info()):
raise
finally:
# 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出
# 或者忽略异常退出
if exc:
exit(context_manager, None, None, None)
# 缺省返回 None,None 在布尔上下文中看做是 False
说明:
- 执行 context_expression,生成上下文管理器 context_manager
- 调用上下文管理器的 enter() 方法;如果使用了 as 子句,则将 enter() 方法的返回值赋值给 as 子句中的 target(s)
- 执行语句体 with-body
- 不管是否执行过程中是否发生了异常,执行上下文管理器的 exit() 方法,exit() 方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句 break/continue/return,则以 None 作为参数调用 exit(None, None, None) ;如果执行过程中出现异常,则使用 sys.exc_info 得到的异常信息为参数调用 exit(exc_type, exc_value, exc_traceback)
- 出现异常时,如果 exit(type, value, traceback) 返回 False,则会重新抛出异常,让with 之外的语句逻辑来处理异常,这也是通用做法;如果返回 True,则忽略异常,不再对异常进行处理
#coding=utf-8
class MyWith:
def __enter__(self):
print("enter")
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
print(exc_type,exc_val,exc_tb)
o=MyWith()
with o:
print("执行with过程!")
输出结果如下:
with对象的调用输出
说明:
1. 没有异常情况下,系统调用__exit__的时候,传递的参数都是None。
2. 其中context_mamager就是with 后的对象,由__enter__返回,并赋值给as后的变量。
修改代码,让__enter__返回一个对象,可以看见as后的变量就是__enter__返回对象。
#coding=utf-8
class MyWith:
def __enter__(self):
print("enter")
return "Hello"
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
print(exc_type,exc_val,exc_tb)
o=MyWith()
with o as b:
print("执行with过程!",b) #b就是__enter__返回的值
执行效果如下:
__enter__的返回值与as后面变量关系
当with过程发生异常,则会传递异常信息,下面是一段with发生异常情况的代码:
#coding=utf-8
class MyWith:
def __enter__(self):
print("enter")
print(1/0) #人为制造一个异常
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
print(exc_type,exc_val,exc_tb)
o=MyWith()
with o as b:
print("执行with过程!",b) #b就是__enter__返回的值
由于exit中没有处理异常,所以会转移异常给调用者,下面是运行的异常输出:
没有在exit处理异常的情况
二、with与异常处理
通过__exit__可以确定是否需要调用者处理异常。返回True,不需要处理,返回False需要处理
下面是with执行过程产生异常的情路:
#coding=utf-8
import traceback
class MyWith:
def __enter__(self):
print("enter")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
print("异常类型",exc_type)
print("异常值", exc_val)
print("异常跟踪",exc_tb)
traceback.print_tb(exc_tb)
print(":",traceback.format_tb(exc_tb))
#return False #需要调用者处理异常
return True #调用者不处理异常
def biz(self):
print(1 / 0) # 人为制造一个异常
o=MyWith()
with o as b:
#b.biz()
print(1 / 0) # 人为制造一个异常
下面是输出效果,可以观察__exit__传递的异常信息。
异常传递与异常信息
提示:异常的相关处理可以使用traceback模块中的函数。
三、上下文管理器模块contextlib
使用contextlib可以达到一样的效果:
from contextlib import contextmanager
@contextmanager
def TheWith():
try:
print('enter')
yield '返回的as后面的值'
print("exit")
except Exception as e:
print(e)
print("这里做关闭工作")
with TheWith() as w:
print("With过程:",w)
print(1/0)
运行的效果如下:
上下文库的使用效果
这里yield生成器前等于enter,yield生成器后等于exit。
如果有异常,可以在with对象内部处理,不用再with过程中处理。
下面是一段简洁代码可以说明contextlib的执行过程:
@contextmanager
def TheWith():
print('enter')
yield '返回的as后面的值'
print("exit")
with TheWith() as w:
print("With过程:",w)
运行效果如下:
contextmanager控制下的执行过程
四、with的典型应用
1. 文件操作异常的正常关闭
#coding=utf-8
#传统写法
file = open("with_try.py")
try:
data = file.read()
print(data)
finally:
file.close()
#简洁写法
with open("with_try.py") as file:
data = file.read()
print(data)
2. 使用绘图上下文
这种使用方式在传统的c中使用比较广泛,但是异常发生需要单独的处理机制,在python中使用with得到很好的处理。
相关的例子代码,可以参考我关于Kivy的系列文章。
资源
本文使用的代码的下载地址: https://github.com/QiangAI/PythonSkill/tree/master/AdvPython/03with
网友评论