Python核心基础~上下文管理器

78 阅读4分钟

起源

首先我们要明白,我们为什么需要上下文管理器?上下管理器必然有其应用场景,不然他为什么会被创造出来了,存在既有理由,上下文管理器的出现是为了解决资源释放的问题,比如数据库的连接,我们都知道如果数据库连接过多,不仅会导致资源占用,甚至可能导致数据库崩溃。于是上下文管理器就优雅的解决了这个问题。

使用

说上下文管理器可能大家比较陌生,但是如果说起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。为真就是处理了,无论有没有处理逻辑,为假就是没处理,就算有处理逻辑一样不好使。

用途

通过上下文管理器被创造出来的原因我们就知道,他是专门用来处理资源释放问题的。所以只要有资源管理的地方,就会有上下文管理器。

总结

本文介绍上下文管理器的起源,和基本使用。以及一个关于异常处理的注意事项。 上下文管理器: 基于类:灵活,但是前摇很大 基于生成器:简单,开箱即用