Python 中的上下文管理器和with 语句
两篇不错的文章:
浅谈 Python 的 with 语句
上下文管理器
上下文管理
当一个对象同时实现了 __enter__
和 __exit__
方法,它就属于上下文管理的对象。
class Point:
def __init__(self, name):
self.name = name
def __enter__(self):
print("{} 进入上下文".format(self.name))
def __exit__(self, exc_type, exc_val, exc_tb):
print("{} 离开上下文".format(self.name))
with Point("silen"):
print("执行体")
输出内容
silen 进入上下文
执行体
silen 离开上下文
with
可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作,需要注意的是,with
并不开启一个新的作用域。
上下文管理的安全性
使用sys.exit(1)
将with
异常退出。
import sys
class Point:
def __init__(self, name):
self.name = name
def __enter__(self):
print("{} 进入上下文".format(self.name))
def __exit__(self, exc_type, exc_val, exc_tb):
print("{} 离开上下文".format(self.name))
with Point("silen"):
print("异常退出")
sys.exit(1)
print("执行体")
执行结果
silen 进入上下文
异常退出
silen 离开上下文
从执行结果可以看出,哪怕是sys.exit(1)
退出了Python
环境,依然执行了__exit__
函数,说明上下文管理很安全
as 做了什么
使用with
的时候,经常在在后面跟上一个as
起到什么作用呢?
import sys
class Point:
def __init__(self, name):
self.name = name
def __enter__(self):
print("{} 进入上下文".format(self.name))
return self.name
def __exit__(self, exc_type, exc_val, exc_tb):
print("{} 离开上下文".format(self.name))
# with Point("silen"):
# print("异常退出")
# sys.exit(1)
# print("执行体")
p = Point("silen")
with p as pp:
print(p == pp)
print(p is pp)
print(id(p), id(pp))
print("p是{} , pp 是 {}".format(p,pp))
执行结果
silen 进入上下文
False
False
2172855121808 2172855628976
p是<__main__.Point object at 0x000001F9E841F790> , pp 是 silen
silen 离开上下文
可以看出,with
语法会调用with
后面对象的的__enter__
方法,如果有as
则将__enter__
函数的返回值赋给as
子句的变量,上例中等价于pp=p.__enter__().
。
__exit__
方法的参数
__exit__(self, exc_type, exc_val, exc_tb)
方法有三个参数,都跟异常有关,如果with
上下文退出时,没有异常,这三个参数的值都为None
,如果有异常时:
exc_type
为异常类型exc_val
为异常值exc_tb
为异常的追踪信息,tb
为traceback
的简写
如果__exit__
方法返回一个等效True
的值,则压制异常,否则,继续抛出异常。import sys
class Point:
def __init__(self, name):
self.name = name
def __enter__(self):
print("{} 进入上下文".format(self.name))
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("exc_type 是 {}", exc_type)
print("exc_val 是 {}", exc_val)
print("exc_tb 是 {}", exc_tb)
print("{} 离开上下文".format(self.name))
return None # 默认return None
with Point(“silen”):
print(“异常退出”)
sys.exit(1)
print(“执行体”)
p = Point(“silen”)
with p as pp:print("开始with")
raise Exception("出异常了")
print("结束with")
输出内容如下,可以看出,异常被抛出来了,
Traceback (most recent call last):
File "D:/ws/python/start/src/base/面向对象/上下文对象.py", line 27, in <module>
raise Exception("出异常了")
Exception: 出异常了
silen 进入上下文
开始with
exc_type 是 {} <class 'Exception'>
exc_val 是 {} 出异常了
exc_tb 是 {} <traceback object at 0x0000025ACF3903C0>
silen 离开上下文
修改上面的代码,返回True
, 异常就看不到了,这里就不贴代码了。可以自行测试。
contextlib
contextlib
的使用比较简单,可以参照文章开头的链接,这里主要看下在使用contextmanager
的时候,异常的处理。
from contextlib import contextmanager
@contextmanager
def show():
print("enter方法内容")
# yield 后面的值作为 __enter__ 函数的返回值
yield "yield返回值"
print("exit方法内容")
with show() as b: # b的值是 yield 返回的
print("with开始了")
print(b)
1/0
print("with结束除了")
上面的代码,执行输出如下:
enter方法内容
with开始了
yield返回值
Traceback (most recent call last):
File "D:/ws/python/start/src/base/面向对象/上下文对象.py", line 42, in <module>
1/0
ZeroDivisionError: division by zero
可以看到函数show
代码中,yield
后面的print("exit方法内容")
并没有执行,如果这句得不到执行,则意味着收尾的工作的无法进行,那么,这个异常应该怎么捕获并处理呢?
from contextlib import contextmanager
@contextmanager
def show():
print("enter方法内容")
try:
# yield 后面的值作为 __enter__ 函数的返回值
yield "yield返回值"
except ZeroDivisionError as e:
print(e)
print("************处理捕获的异常***************")
finally:
print("exit方法内容")
with show() as b:
print("with开始了")
print(b)
1 / 0
print("with结束除了")
看下输出结果,就知道该怎么处理with
体内出现的异常了。
enter方法内容
with开始了
yield返回值
division by zero
************处理捕获的异常***************
exit方法内容
使用上下文管理器的时候,可以根据业务逻辑复杂的程度来选择,如果业务逻辑简单就使用函数式的,如果复杂就使用类方式的即可,
上下文管理器就到这里。
还没有评论,来说两句吧...