# With上下文管理器和魔术方法
# 概述
对于正确的处理涉及到异常的资源管理时,需要使用 try/finally 代码结构,这样的结构一多会导致整体代码结构 很臃肿繁琐,不易读、不美观,因此在 Python2.6 版本推出 with 关键字。
with as 语句是 Pyhton 提供的一种简化语法,适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的清理操作,释放资源。
对于系统资源如文件、数据库连接、socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(释放)该资源。
比如 Python 程序打开一个文件,往文件中写内容,写完之后,就要关闭该文件,如果不关闭会出现什么情况呢?极端情况下会出现 Too many open files 的错误,因为系统允许你打开的最大文件数量是有限的。
同样,对于数据库,如果连接数过多而没有及时关闭的话,就可能会出现 Can not connect to MySQL server Too many connections,因为数据库连接是一种非常昂贵的资源,不可能无限制的被创建。
并且如下面的代码片段,如果代码在执行close之前报错了,那么资源实际上是没有释放的。
file = "test.txt"
def fun1():
"""
普通版
"""
f = open(file, "w")
f.write("hello python")
f.close
Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个 contextmanager 装饰器,更进一步简化上下文管理器的实现方式。基于类和基于 contextmanager 的上下文管理器,这两者在功能上是一致的。只不过,基于类的上下文管理器 更加灵活,适用于大型的系统开发,而基于 contextmanager 的上下文管理器 更加方便、简洁,适用于中小型程序。
# 上下文管理器
任何类实现了 __enter__() 和 __exit__() 方法的对象都可称之为上下文管理器。
上下文管理器对象可以使用 with 关键字。
但是注意,__enter__()方法有时会返回self-即as后的赋值操作-即as是可选的,有时不会,但是__exit__()始终在上下文管理器上调用,而不是在__enter__()方法返回的对象上调用。
with open('file.txt', 'r') as f:
content = f.read()
print(content)
# 基于类的上下文管理器
当我们用类来创建上下文管理器时,必须保证这个类包括方法
__enter__()和方法__exit__()。- 其中,方法
__enter__()返回需要被管理的资源,方法__exit__()里通常会存在一些释放、清理资源的操作 - 方法
__exit__()中的参数exc_type, exc_val, exc_tb”,分别表示exception_type、exception_value 和 traceback。当我们执行含有上下文管理器的 with 语句时,如果有异常抛出,异常的信息就会包含在这三个变量中,传入方法__exit__()。
- 其中,方法
基于类的上下文管理器更加 flexible,适用于大型的系统开发;
class FileManager:
def __init__(self, name, mode):
print('calling __init__ method')
self.name = name
self.mode = mode
self.file = None
def __enter__(self):
print('calling __enter__ method')
self.file = open(self.name, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
print('calling __exit__ method')
if self.file:
self.file.close()
with FileManager('test.txt', 'w') as f:
print('ready to write to file')
f.write('hello world')
## 输出
calling __init__ method
calling __enter__ method
ready to write to file
calling __exit__ method
# 基于生成器的上下文管理器
- 可以使用装饰器 contextlib.contextmanager,来定义自己所需的基于生成器的上下文管理器,用以支持 with 语句。
- 使用基于生成器的上下文管理器时,不再用定义“enter()”和“exit()”方法,但需要加上装饰器 @contextmanager
- 基于生成器的上下文管理器更加方便、简洁,适用于中小型程序。
from contextlib import contextmanager
@contextmanager
def file_manager(name, mode):
try:
f = open(name, mode)
yield f
finally:
f.close()
with file_manager('test.txt', 'w') as f:
f.write('hello world')