前言
首先问下帅气的读者们,当你们需要访问一个文件时候,你们是用什么方式访问的,是open吗?如果是,那么你们有没有觉得每次都需要 ‘手动’ 关闭很麻烦或则忘记关闭很麻烦。今天我们带大家来认识python一个新的魔法---上文管理器。如果你知道那么不要急着走开,自己心里默默的问自己是不是可以回答以下几个问题:
- 上下文管理器是什么?
- 上下文管理器怎么实现,你是否有多种方式来实现呢?
- 为什么要用上下文管理器?
上下文管理器是什么
概念
上下文管理器:Python2.5开始支持的一种语法,用于规定某个对象的使用范围。一旦进入或者离开该使用范围,会有特殊操作被调用 (比如为对象分配或者释放内存)。它的语法形式是with...as..
语法
首先我们先简单的看一段使用上下文管理器的代码:
with open('帅气的读者名单.txt', 'w') as f:
f.write('读者名字')
从上面可以看出语法结构是:
with expre as var:
context
上下文管理器怎么实现
类实现了__enter__
和__exit__
的方法
这里我们就以实现处理文件为例子吧
class OperateFile(object):
def __init__(self, file_name, operate_type):
print('初始化')
self.file_name = file_name
self.operate_type = operate_type
self.fp = None
def __enter__(self):
print('调用 __enter__ ')
self.fp = open(self.file_name, self.operate_type)
return self.fp
def __exit__(self, exc_type, exc_val, exc_tb):
print('调用 __exit__ ')
if self.fp:
self.fp.close()
if __name__ == "__main__":
with OperateFile(path, "w+") as f:
f.write("帅气的读者")
执行结果
初始化
调用 __enter__
调用 __exit__
从上面可以看出,在编写代码时,__enter__返回需要管理的资源,__exit__ 通常做释放资源的操作。
可以简单来说:
__enter__: 进入与此对象相关的上下文。如果存在该方法,with语法会把该方法的返回值作为绑定到as子句中指定的变量上
__exit__: 退出对象对应的上下文
另外注意的是: __exit__()中有四个参数,当程序块中出现异常(exception),__exit__()的参数中exc_type(异常类型), exc_value(异常值), traceback(异常的错误栈信息)用于描述异常。
基于生成器实现上下文管理器
在python中除了通过类实现__enter__
和__exit__
的方法来实现上下文管理器外,我们也可以使用contextlib.contextmanager装饰器来实现基于生成器的上下文管理器,接下来我们就简单看看吧。
这里我们就以实现链接数据库为例子吧
from contextlib import contextmanager
import pymysql
@contextmanager
def db_manager(host, port, user, password, database):
try:
db_mysql = pymysql.connect(host=host, user=user, password=password, database=database, port=port)
yield db_mysql
finally:
db_mysql.close()
这段代码中,函数 db_manager() 是一个生成器,当我们执行 with 语句时,便会打开文件,并返回文件对象 db_mysql;当 with 语句执行完后,finally block 中的关闭文件操作便会执行。
注意:基于生成器定义的上下文管理需要使用装饰器 @contextmanager,不再使用生成器协议方法。
两者区别
使用类来实现相较于生成器实现更加灵活,在复杂度更高更大型的项目可以使用,反之可以使用生成器来实现
为什么要用上下文管理器
第一,我们可以更加优雅的操作(创建/获取/释放)资源,如文件操作、数据库连接等等;
第二,可以避免内存泄露,首先我们看下面的这个例子
for x in range(10000000):
f = open('./帅气的读者名单.txt', 'w')
f.write('帅气的读者名字')
因为程序中同时打开了太多的文件,占据了太多的资源,造成系统崩溃。所以报错
OSError: [Errno 23] Too many open files in system: '帅气的读者名单.txt'
ps:在这里我简单的说句,你别说怎么可能会出现这样的错误,我个人的角度,人是一个及其不可控的因素,很多bug都是不小心引起的
第三,可以更加优雅的处理异常
class ErrorCapture():
def __enter__(self):
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)
return True
def operate(self):
1 / 0
with ErrorCapture() as res:
res.operate()
在这个例子里,我们可以选择展示告警(也可以根据情况设定不同返回信息),也可以选择不展示直接写入日志等等的处理方式
总的来说使用上下文管理器可以:
- 提高代码复用率;
- 提高代码可读性;
- 避免一些人为因素引起的问题