Python3.7学习笔记27-上下文管理器(context manager)
文件的输入输出,数据库的连接与断开,这种是常见的资源管理操作。因为资源有限。在这类场景中。如果在使用过这些资源但是没有得到释放的话。会造成资源泄露。轻者使系统缓慢,重则会使系统奔溃。
一、基于场景
如下作死的代码。我们一次打开100000000个文件。但是只往里面写。而不选择关闭文件。不仅最后会报资源泄露的问题。严重的话会让电脑死机。所以不要执行下面的代码。
# 典型的没做关闭处理。会造成资源泄漏
for x in range(1000000000):
f = open('test.txt','w')
f.write('test')
# 大概率会报
OSError:[Errno 23] Too many open........
# 而且你电脑的cpu会狂飙。如果是windows系统或者差一点的。死机也是很正常的。
为了解决上面那种有可能人为的出错。在python中引入了上下文管理机制。它能够帮助你自动分配资源并且释放资源。最典型的应用就是with语句。如下上面的代码可以重写。with 语句 在每次执行完成。内部都会相当于f.close() 关闭文件。释放资源。
# 没事也别运行这种代码。不然新建这么多的文件删的蛋疼。测试的话循环次数个位数。
for x in range(100000000):
with open('test.txt','w') as f:
f.write('test')
当然还有一种写法。还记得我们学的异常吧。不管前面是否报错。一定要执行的finally
for x in range(10000000):
f = open('test.txt', 'w')
try:
f.write('hello')
finally: # try 这里的代码块就算异常了。也会执行关闭文件的操作
f.close()
但是对比一下代码的简洁度。我们还是倾向于用with 语句。类似其他场景比如数据库 进程锁也是一样的。可以自己试试。
二、基于类的上下文管理
在创建类的上下文管理器的时候,必须保证这个类含有 __enter__() __exit__()。如下。我们也算了解了with内部逻辑。其实是封装了2个方法。把管理资源的功能都赋予他们。我们只要关心我们自己本身的操作如写入读出等。
class FileManager:
def __init__(self, name, mode):
print('初始化类变量')
self.name = name
self.mode = mode
self.file = None
def __enter__(self):
"""
返回需要被管理的资源
:return:
"""
print('调用:返回需要被管理的资源函数')
self.file = open(self.name, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
"""
释放 清理资源的操作。当有异常抛出。会把信息传入到这3个变量中
:param exc_type:exception_type
:param exc_val:exception_vale
:param exc_tb:traceback
:return:
"""
print('调用释放资源函数')
if self.file:
self.file.close()
if exc_type:
print(f'exception_type: {exc_type}') # 以 f开头表示在字符串内支持大括号内的python 表达式
print(f'exc_value: {exc_val}')
print(f'exc_traceback: {exc_tb}')
with FileManager('test.txt', 'w') as f:
print('测试写入文件')
f.write('我是被写入的文字')
raise Exception('主动触发异常').with_traceback(None)
如下最常见的除了文件管理还有数据库的连接
import pymysql
class MySql:
def __init__(self, host, user, password, port, db):
"""
初始化参数值
:param host: 地址
:param user: 账号
:param password: 密码
:param port: 端口号
:param db: 数据库名称
"""
self.host = host
self.user = user
self.password = password
self.port = int(port)
self.db = db
self.value = True
def __enter__(self):
"""
连接数据库 返回实例化对象
:return:
"""
try:
self.sqlConn = pymysql.connect(
host=self.host,
user=self.user,
password=self.password,
db=self.db,
charset="utf8",
port=self.port)
self.sqlCursor = self.sqlConn.cursor()
except Exception as e:
self.value = False
self.err = e
return self
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""
关闭数据库连接释放资源
:param exc_type:
:param exc_val:
:param exc_tb:
:return:
"""
try:
self.sqlCursor.close()
except:
pass
def select(self,sql):
"""
查询数据
:param sql:
:return: 正常返回=[{},{},{}.....] 异常直接返回错误信息
"""
if self.value:
try:
self.sqlCursor.execute(sql)
cur = self.sqlCursor.description # 查询返回值的字段名称
result = self.sqlCursor.fetchall() # 查询返回的值
except BaseException as e:
return e
data = []
# 把查询结果值解析成预期数据类型的格式
try:
for i in range(len(result)):
lie = {}
for j in range(len(cur)):
lie[cur[j][0]] = result[i][j]
data.append(lie)
except BaseException as e:
return e
return data
else:
return self.err
def update(self,sql):
"""
更新数据
:param sql:
:return:
"""
try:
# 执行SQL语句
self.sqlCursor.execute(sql)
# 提交到数据库执行
self.sqlConn.commit()
except BaseException as e:
# 发生错误时回滚
self.sqlConn.rollback()
return e
return
def insert(self,sql):
"""
新增数据
:param sql:
:return:
"""
try:
self.sqlCursor.execute(sql)
self.sqlConn.commit()
except BaseException as e:
self.sqlConn.rollback()
return e
return