起源
首先我们要明白,我们为什么需要上下文管理器?上下管理器必然有其应用场景,不然他为什么会被创造出来了,存在既有理由,上下文管理器的出现是为了解决资源释放的问题,比如数据库的连接,我们都知道如果数据库连接过多,不仅会导致资源占用,甚至可能导致数据库崩溃。于是上下文管理器就优雅的解决了这个问题。
使用
说上下文管理器可能大家比较陌生,但是如果说起with
语句块大家就不陌生了吧,没错上下文管理器 + with
语句就可以优雅的实现资源释放的问题。
基于类
我们来看看如何定义上下文管理器,这里需要用到Python的魔术方法,这里简单说明一下,什么是魔术方法,现在可以简单的认为是内置方法。以后咱细讲。
import pymysql
class MySQLHelper:
def __init__(self, database, port, username, password):
print("MySQLHelper初始化")
self.database = database
self.port = port
self.username = username
self.password = password
def __enter__(self):
print("MySQLHelper enter")
self.connect = pymysql.connect(
database=self.database,
port=self.port,
user=self.username,
password=self.password
)
return self.connect
def __exit__(self, exc_type, exc_val, exc_tb):
print("MySQLHelper exit")
if self.connect:
self.connect.close()
if __name__ == '__main__':
sql = "select * from sys_config"
with MySQLHelper(database='sys', port=3306, username='root', password='lwhlx2003') as con:
cursor = con.cursor()
cursor.execute(sql)
for info in cursor.fetchall():
print(info)
print("查询完毕")
//output:
MySQLHelper初始化
MySQLHelper enter
('diagnostics.allow_i_s_tables', 'OFF', datetime.datetime(2023, 2, 16, 21, 55, 46), None)
('diagnostics.include_raw', 'OFF', datetime.datetime(2023, 2, 16, 21, 55, 46), None)
('ps_thread_trx_info.max_length', '65535', datetime.datetime(2023, 2, 16, 21, 55, 46), None)
('statement_performance_analyzer.limit', '100', datetime.datetime(2023, 2, 16, 21, 55, 46), None)
('statement_performance_analyzer.view', None, datetime.datetime(2023, 2, 16, 21, 55, 46), None)
('statement_truncate_len', '64', datetime.datetime(2023, 2, 16, 21, 55, 46), None)
查询完毕
MySQLHelper exit
通过这段代码我们可以知道,上下文管理器就两个核心的魔术方法,第一个就是__enter__()
另一个就是 __exit__()
。__enter__()
方法用于初始化我们需要管理的资源而__exit__()
方法用于回收我们管理的资源。借助于这两个魔术方法,我们也可以很优雅的实现和open()
函数一样的功能
基于生成器
其实对于上下文管理器,Python还提供了另外一种创建生成器的方法 那就是
装饰器(contextmanager
) + 生成器 这里需要大家明白什么是装饰器以及生成器。
from contextlib import contextmanager
@contextmanager # 装饰器
def my_open(name, mode):
file = None
try:
file = open(name, mode)
yield file
finally:
if file:
print("file关闭")
file.close()
if __name__ == '__main__':
with my_open("test.txt", "w") as file:
file.write("pandaer学Python")
print("写入完毕")
output:
file关闭
写入完毕
注意事项
异常处理问题
if __name__ == '__main__':
sql = "select * from sys_config"
with MySQLHelper(database='sys', port=3306, username='root', password='lwhlx2003') as con:
cursor = con.cursor()
cursor.execute(sql)
for info in cursor.fetchall():
print(info)
raise Exception("我是故意的异常") //注意这里,其他代码同基于类的那段代码
print("查询完毕")
Traceback (most recent call last):
File "/Users/mac/Code/Python/PythonNote/上下文管理器/context_manager.py", line 49, in <module>
raise Exception("我是故意的异常")
Exception: 我是故意的异常
def __exit__(self, exc_type, exc_val, exc_tb):
print("MySQLHelper exit")
if self.connect:
self.connect.close()
return True // 加上True 这里有变化其他同基于类的代码块
output:
MySQLHelper初始化
MySQLHelper enter
('diagnostics.allow_i_s_tables', 'OFF', datetime.datetime(2023, 2, 16, 21, 55, 46), None)
MySQLHelper exit
可以很明显看出来,异常被吞了,程序也停止了,其实真正的执行逻辑是这样这样的。发生异常是,会调用 __exit__()
函数,将异常信息放入exc_type
, exc_val
, exc_tb
中,让我们进行处理,但是系统对于是否处理异常只关注于 __exit__()
函数的返回值是否为 True
。为真就是处理了,无论有没有处理逻辑,为假就是没处理,就算有处理逻辑一样不好使。
用途
通过上下文管理器被创造出来的原因我们就知道,他是专门用来处理资源释放问题的。所以只要有资源管理的地方,就会有上下文管理器。
总结
本文介绍上下文管理器的起源,和基本使用。以及一个关于异常处理的注意事项。 上下文管理器: 基于类:灵活,但是前摇很大 基于生成器:简单,开箱即用