Python 上下文管理器

目录

1、上下文管理器

1.1、为什么需要上下文管理器?

1.2、上下文管理器的原理

1.2.1、实现协议

1.2.2、异常处理

1.2.3、with语句执行原理

1.3、上下文管理器的实际应用示例

1.4、contextlib模块

1.4.1、contextmanager 装饰器

1、上下文管理器

1.1、为什么需要上下文管理器?

  • 在日常编码中我们经常会操作一些资源,比如文件对象、数据库连接、Socket连接等,资源操作完了之后,不管操作的成功与否,都要关闭该资源,避免出现内存泄漏,CPU资源占用的问题。如果没有上下文管理器,我们关闭资源就需要手动关闭,但是这里可能会被遗漏,所以我们就需要一个自动帮我们关闭资源的东西,那就是上下文管理器。
  • 示例:手动关闭资源的场景(手动关闭文件)
try:
    # 打开文件
    f = open("XXX", "w", encoding='utf-8')
    f.write("hello world")
except Exception as e:
    # 处理操作文件错误
    print(f"操作文件错误,错误原因:{e!r}")
finally:
    # 关闭文件
    f.close()

1.2、上下文管理器的原理

1.2.1、实现协议

上下文管理器协议:是指要实现对象的 __enter__() 和 __exit__() 方法。

  • __enter__(self):进入上下文管理器的运行时上下文。在with语句块执行之前调用,其返回值将绑定到with语句的as子句指定的变量上。
  • __exit__(self, exc_type, exc_val, exc_tb):退出上下文管理器的运行时上下文。在with语句块执行完毕后调用,无论是正常结束还是异常结束。exc_typeexc_valexc_tb分别表示异常类型、异常值和traceback信息,如果with语句块没有异常发生,则这三个参数都将为None

示例:自定义实现一个上下文管理器

class MyContextManager:  
    def __enter__(self):  
        print("Enter the context")  
        return self  
      
    def __exit__(self, exc_type, exc_val, exc_tb):  
        print("Exit the context")  
        if exc_type is not None:  
            print(f"Error occurred: {exc_type}")  
        return False  # 如果为True,会阻止异常的抛出  
  
with MyContextManager() as cm:  
    print("Inside the context")  
    # raise Exception("Something bad happened")  # 你可以尝试取消注释这行代码,看看`__exit__`如何处理异常

1.2.2、异常处理

  • 1、如果上下文是无异常地退出的,三个参数都将为None。
  • 2、如果 with_body中出现了异常,并且希望方法屏蔽此异常(即避免其被传播),则应当返回真值即返回True,没有显示返回True默认返回None(False)。 否则的话,异常将在退出方法__exit__时按正常流程处理。
  • 3、请注意__exit__()方法不应该重新引发被传入的异常,这是调用者的责任。如果 with_body 的退出由异常引发,并且__exit__()的返回值等于 False,那么这个异常将被重新引发一次;如果 __exit__() 的返回值等于 True,此时认为异常在__exit__中以及处理过了,那么这个异常就被无视掉,继续执行后面的代码。

示例1:__exit__() 返回True,无视异常,继续执行后续代码

class MyContextManager:
    def __enter__(self):
        print("Enter the context")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Exit the context")
        if exc_type is not None:
            print(f"Error occurred: {exc_type}")
        return True  # 如果为True,会阻止异常的抛出


with MyContextManager() as cm:
    print("Inside the context")
    raise Exception("Something bad happened")

示例2:__exit__() 返回False,抛出异常,中断执行代码

class MyContextManager:
    def __enter__(self):
        print("Enter the context")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Exit the context")
        if exc_type is not None:
            print(f"Error occurred: {exc_type}")
        return False  # 如果为True,会阻止异常的抛出


with MyContextManager() as cm:
    print("Inside the context")
    raise Exception("Something bad happened")

1.2.3、with语句执行原理

上下文管理器with语法:

with context_obj [as obj]:
    with_body

执行原理:

  • 1、使用with语句使用的时,上下文管理器会自动调用__enter__方法,然后进入运行时上下文环境,如果有as 从句,返回自身或另一个与运行时上下文相关的对象,值赋值给obj。
  • 2、当with_body执行完毕退出with语句块或者with_body代码块出现异常,则会自动执行__exit__方法,并且会把对于的异常参数传递进来。
  • 3、如果__exit__函数返回True。则with语句代码块不会显示的抛出异常,终止程序,如果返回None或者False,异常会被主动raise,并终止程序。

示例:自定义实现with open() 操作文件

# 使用上下文管理器实现上述文件操作
class MyOpenFile(object):
    def __init__(self, filename, mode='r'):
        self.file = open(filename, mode)

    def __enter__(self):
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()


with MyOpenFile('hello', 'w') as f:
    f.write("hello world")

1.3、上下文管理器的实际应用示例

需求:实现数据库资源操作包括创建资源和自动释放资源

import pymysql
class MyDataBase(object):
    def __init__(self, host, username, password, db, port):
        self.db = pymysql.connect(host=host, username=username, password=password, database=db, port=port)
        self.cursor = self.db.cursor()

    def query_one_data(self, sql):
        self.cursor.execute(sql)
        result = self.cursor.fetchone()
        return result

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.cursor.close()
        self.db.close()

def test():
    sql = "select * from t_student where id = 1"
    with MyDataBase() as db:
        res = db.query_one_data(sql)
        print(res)

if __name__ == "__main__":
    test()

1.4、contextlib模块

contextlib 是 Python 的一个标准库模块,它提供了用于创建上下文管理器的装饰器和上下文管理器类。上下文管理器主要用于 with 语句,帮助自动管理资源,如文件操作、线程锁的获取与释放等,使得代码更加简洁、安全。

contextlib 模块中最常用的工具是 contextmanager 装饰器

1.4.1、contextmanager 装饰器

  • contextmanager 装饰器用于将一个简单的生成器函数转换成上下文管理器。这允许你避免创建一个类或单独的方法来实现 __enter__() 和 __exit__() 方法。
  • 被装饰的函数被调用的时候必须返回一个生成器(被装饰器修饰的函数必须是一个生成器),而且这个生成器只生成一个值,如果有as的话,该值将绑定到with语句as子句的目标obj中。

示例1:

from contextlib import contextmanager  

@contextmanager  
def my_context():  
    print('Enter')  
    try:  
        yield  
    finally:  
        print('Exit')  

with my_context():  
    print('Inside the context')

根据输出结果,可以知道执行的流程:

  • 1、先输出yield前的输出语句;
  • 2、然后再是my_context()函数的输出语句,
  • 3、最后是yield后面的输出语句。

等价于下面的写法:

def __enter__(self):
    print('Enter')


def __exit__(self, exc_type, exc_val, exc_tb):
    print('Exit') 

示例2,使用contextmanager 装饰器实现1.3中数据库的管理操作:

import contextlib, pymysql
class MyDataBase(object):
    def __init__(self, host, username, password, db, port):
        self.db = pymysql.connect(host=host, username=username, password=password, database=db, port=port)
        self.cursor = self.db.cursor()

    def query_one_data(self, sql):
        self.cursor.execute(sql)
        result = self.cursor.fetchone()
        return result

@contextlib.contextmanager
def db_query_one():
    db = MyDataBase("127.0.0.1", "test", "123456", "student", 3306)
    yield db

def test():
    sql = "select * from t_student where id = 1"
    with db_query_one() as db:
       res =  db.query_one_data(sql)
       print(res)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值