1. 定义
1.1 上下文协议
__enter__()和__exit__()方法构成了上下文协议
1.2 上下文管理器
实现了上下文协议的对象,即为上下文管理器
2. 优点
- 提高代码的复用率
- 提高代码的优雅度
- 提高代码的可读性
3. 使用说明
3.1 __enter__(self)说明
若存在类方法, 则__enter__()方法需要返回self, 否则无法调用对应方法
# __enter__()返回self
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __enter__(self):
print("-----enter.-----")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("-----exit.-----")
def division(self):
return self.a / self.b
if __name__ == '__main__':
with Demo(1, 1) as test:
print(test.division())
# 执行结果
>>> -----enter.-----
>>> 1.0
>>> -----exit.-----
# __enter__()不返回self
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __enter__(self):
print("-----enter.-----")
# return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("-----exit.-----")
def division(self):
return self.a / self.b
if __name__ == '__main__':
with Demo(1, 1) as test: # 由于未返回self, 则test实际是空对象
print(test.division())
# 执行结果
>>> -----enter.-----
>>> -----exit.-----
>>> Traceback (most recent call last):
>>> File "E:/StudyPython/study_context_manager/test.py", line 28, in
>>> print(test.division())
>>> AttributeError: 'NoneType' object has no attribute 'division'
3.2 __exit__(self, exc_type, exc_val, exc_tb)方法说明
- __exit__()方法默认返回False.
- 若__exit__()方法返回False, 则在管理器执行过程中发生的异常将被抛出
- 若__exit__()方法返回True, 则在管理器执行过程中发生的异常不被抛出
- exc_type: 异常类型, 若无异常信息, 值为None
- exc_val: 异常信息, 若无异常信息, 值为None
- exc_tb: 异常堆栈, 若无异常信息, 值为None
PS: 若__exit__()方法返回True, 但是__enter__()和__exit__()方法中存在异常, 异常也会被抛出
# __exit__()方法默认返回值示例
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __enter__(self):
print("-----enter.-----")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("-----exit.-----") # 默认返回False, 异常将被抛出
def division(self):
return self.a / self.b
if __name__ == "__main__":
with Demo(1, 0) as test:
print(test.division())
# 执行结果
>>> -----enter.-----
>>> -----exit.-----
>>> Traceback (most recent call last):
>>> File "E:/StudyPython/study_context_manager/test.py", line 28, in
>>> print(test.division())
>>> File "E:/StudyPython/study_context_manager/test.py", line 23, in division
>>> return self.a / self.b
>>> ZeroDivisionError: division by zero
# __exit__()方法返回False示例
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __enter__(self):
print("-----enter.-----")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("-----exit.-----")
return False # 返回False, 异常将被抛出
def division(self):
return self.a / self.b
if __name__ == "__main__":
with Demo(1, 0) as test:
print(test.division())
# 执行结果
>>> -----enter.-----
>>> -----exit.-----
>>> Traceback (most recent call last):
>>> File "E:/StudyPython/study_context_manager/test.py", line 28, in
>>> print(test.division())
>>> File "E:/StudyPython/study_context_manager/test.py", line 23, in division
>>> return self.a / self.b
>>> ZeroDivisionError: division by zero
# __exit__()方法返回True示例
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __enter__(self):
print("-----enter.-----")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("-----exit.-----")
return True # 返回True, 异常不被抛出
def division(self):
return self.a / self.b
if __name__ == "__main__":
with Demo(1, 0) as test:
print(test.division())
# 执行结果
>>> -----enter.-----
>>> -----exit.-----
# __exit__()方法参数说明示例
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
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) # 异常堆栈
return True
def division(self):
return self.a / self.b
if __name__ == "__main__":
with Demo(1, 0) as test:
print(test.division())
# 执行结果
>>> -----enter.-----
>>> -----exit.-----
>>>
>>> division by zero
>>>
# __enter__()方法中存在异常示例
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __enter__(self):
print("-----enter.-----")
raise ValueError("test.")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("-----exit.-----")
print(exc_type)
print(exc_val)
print(exc_tb)
return True
def division(self):
return self.a / self.b
if __name__ == "__main__":
with Demo(1, 0) as test:
print(test.division())
# 执行结果
>>> -----enter.-----
>>> Traceback (most recent call last):
>>> File "E:/StudyPython/study_context_manager/test.py", line 32, in
>>> with Demo(1, 0) as test:
>>> File "E:/StudyPython/study_context_manager/test.py", line 17, in __enter__
>>> raise ValueError("test.")
>>> ValueError: test.
# __exit__()方法中存在异常示例
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
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)
raise ValueError("test.") # 由于__exit__()存在异常, 上下文管理执行过程中的异常将被一并抛出
return True
def division(self):
return self.a / self.b
if __name__ == "__main__":
with Demo(1, 0) as test:
print(test.division())
# 执行结果
>>> -----enter.-----
>>> -----exit.-----
>>>
>>> division by zero
>>>
>>> Traceback (most recent call last):
>>> File "E:/StudyPython/study_context_manager/test.py", line 33, in
>>> print(test.division())
>>> File "E:/StudyPython/study_context_manager/test.py", line 28, in division
>>> return self.a / self.b
>>> ZeroDivisionError: division by zero
>>>
>>> During handling of the above exception, another exception occurred:
>>>
>>> Traceback (most recent call last):
>>> File "E:/StudyPython/study_context_manager/test.py", line 33, in
>>> print(test.division())
>>> File "E:/StudyPython/study_context_manager/test.py", line 24, in __exit__
>>> raise ValueError("test.")
>>> ValueError: test.
4. 实现方式
- 基于类实现
- 基于装饰器实现
PS: 上述所有示例, 全是基于类实现, 将不再做示例演示
4.1 基于装饰器实现
yield 关键字之前的部分等价于__enter__()方法, 之后的部分等价于__exit__()方法
# 不对yield进行try操作, 上下文管理中异常将被抛出, 且不能执行yield之后的代码
from contextlib import contextmanager
@contextmanager
def demo(a, b):
print("-----enter.-----")
c = a / b
yield c
print("-----exit.-----")
if __name__ == '__main__':
with demo(1, 1) as c:
print(c)
raise ValueError(123)
# 执行结果
>>> -----enter.-----
>>> 1.0
>>> Traceback (most recent call last):
>>> File "E:/StudyPython/study_context_manager/test.py", line 32, in
>>> raise ValueError(123)
>>> ValueError: 123
# 对yield进行try操作, 且上下文管理中异常被抛出示例, 等价于__enter__()方法返回False
from contextlib import contextmanager
@contextmanager
def demo(a, b):
print("-----enter.-----")
c = a / b
try:
yield c
except Exception as e:
raise e
finally:
print("-----exit.-----")
if __name__ == "__main__":
with demo(1, 1) as c:
print(c)
raise ValueError(123)
# 执行结果
>>> -----enter.-----
>>> 1.0
>>> -----exit.-----
>>> Traceback (most recent call last):
>>> File "E:/StudyPython/study_context_manager/test.py", line 38, in
>>> raise ValueError(123)
>>> ValueError: 123
# 对yield进行try操作, 且上下文管理中异常被抛出示例, 等价于__enter__()方法返回True
from contextlib import contextmanager
@contextmanager
def demo(a, b):
print("-----enter.-----")
c = a / b
try:
yield c
except Exception as e:
print(type(e))
print(e)
finally:
print("-----exit.-----")
if __name__ == "__main__":
with demo(1, 1) as c:
print(c)
raise ValueError(123)
# 执行结果
>>> -----enter.-----
>>> 1.0
>>>
>>> 123
>>> -----exit.-----
5. 其他用法
5.1 contextlib.closing()
# closing()代码
class closing(AbstractContextManager):
"""Context to automatically close something at the end of a block.
Code like this:
with closing(.open()) as f:
is equivalent to this:
f = .open()
try:
finally:
f.close()
"""
def __init__(self, thing):
self.thing = thing
def __enter__(self):
return self.thing
def __exit__(self, *exc_info):
self.thing.close()
# 代码分析
closing类本身为基于类实现的上线文管理器, 在__exit__()方法中调用thing变量的close()方法. 所以thing变量必须要拥有close()方法.
但是__exit__()方法并没有return, 所以上线文管理器执行过程中的异常将被抛出. 若要抛出异常需要重写closing类中的__exit__()方法.
# 用法示例
from contextlib import closing
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
def division(self):
return self.a / self.b
def close(self):
print("-----exit.-----")
return True
if __name__ == '__main__':
with closing(Demo(1, 1)) as test:
print(test.division())
# 执行结果
>>> 1.0
>>> -----exit.-----
# 抛出异常的情况
from contextlib import closing
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
def division(self):
return self.a / self.b
def close(self):
print("-----exit.-----")
return True
if __name__ == '__main__':
with closing(Demo(1, 0)) as test:
print(test.division())
# 执行结果
>> -----exit.-----
>> Traceback (most recent call last):
>> File "E:/StudyPython/study_context_manager/test.py", line 24, in
>> print(test.division())
>> File "E:/StudyPython/study_context_manager/test.py", line 17, in division
>> return self.a / self.b
>> ZeroDivisionError: division by zero
# 重写closing类中的__exit__()方法
from contextlib import closing
class MyClosing(closing):
def __exit__(self, exc_type, exc_val, exc_tb):
return self.thing.close()
class Demo(object):
def __init__(self, a, b):
self.a = a
self.b = b
def division(self):
return self.a / self.b
def close(self):
print("-----exit.-----")
return True
if __name__ == '__main__':
with MyClosing(Demo(1, 0)) as test:
print(test.division())
# 执行结果
>> -----exit.-----