上下文管理器

上下文管理器提供了一种申请资源和释放资源的方法,并且能保证离开上下文管理器时,一定会执行释放资源的操作,从语言角度来讲仅可能避免了资源的泄露。

  • “资源泄漏” 是一个统称,不仅包含了内存泄漏,还包含了文件描述符或句柄泄漏、数据库连接泄漏等,所以后续本文统一使用 “资源泄漏”。


在进入上下文管理器时,先申请资源,然后执行业务逻辑代码,不管在执行代码时是否抛出异常,在退出上下文管理器时,都会执行释放资源的操作。

上下文管理器类

当进入上下文管理器执行with语句时,python调用对象的__enter__方法,离开with时,调用对象的__exit__方法。

  • __enter__方法一般时申请对象所需资源,然后返回一个对象,这个对象会被赋值给as之后的那个变量,返回的对象一般时self
  • 类的构造方法和__enter__方法需要捕获所有可能的异常,否则__exit__方法不会被调用。在构造方法和__enter__函数中不能抛出任何异常,否则会造成资源泄露。
  • 成功进入上下文管理器代码块时,在执行业务逻辑代码时,无论发生什么异常都会调用__exit__来释放资源
  • 如果__exit__返回True, 那么将忽略__exit__中的任何异常将会正确退出上下文管理器
import time
class Context:
    def __init__(self, n):
        self._data = [i for i in range(n)]

    def __enter__(self):
        print("申请资源进入上下文管理器")
        return self # 返回一个对象赋值给as后的变量

    def __exit__(self, exc_type, exc_instance, traceback):
        """
        exc_type: 异常类型
        exc_instance: 异常实例
        traceback: 回溯
        如果没有异常,三个参数都为None
        """
        print("释放资源推出上下文管理器")
    
    @property
    def data(self):
        return self._data


if __name__ == "__main__":
    
    with Context(10) as context:
        print("执行业务逻辑代码")
        for i in context.data:
            print(i, end=' ')
        print("\n业务逻辑代码执行结束")

__exit__方法

__exit__必须接收3个位置参数:

  1. 异常类型:exc_type
  2. 异常实例: exc_instance
  3. 回溯选择:traceback

如果__exit__接收一个异常,有三种选择:

  1. 向上传播异常,返回False
  2. 终止异常, 返回True
  3. 抛出不同的异常
import time
class Context:
    def __init__(self, n):
        self._data = [i for i in range(n)]

    def __enter__(self):
        print("申请资源进入上下文管理器")
        return self # 返回一个对象赋值给as后的变量

    def __exit__(self, exc_type, exc_instance, traceback):
        """
        exc_type: 异常类型
        exc_instance: 异常实例
        traceback: 回溯
        如果没有异常,三个参数都为None
        """
        print("释放资源推出上下文管理器")
        if exc_instance:
            print("捕获%s异常"%exc_instance)
        if exc_type == ZeroDivisionError:
            print("除0异常, 选择中止异常")
            return True 
        if exc_type == ValueError:
            raise TypeError("碰到ValueError时抛出类型错误异常")
        print("向上传播异常")
        return False 
    
    @property
    def data(self):
        return self._data


if __name__ == "__main__":
    
    with Context(10) as context:
        # 除0不会报错,在__exit__方法中对于除0异常选择终止异常
        # print(10/0)  
        # 数值错误异常会转为类型错误异常
        # raise ValueError("抛出数值错误异常")  
        # 其他异常会选择向上传播异常
        raise IndexError()
    

让函数支持上下文管理器

使用contextlib.contextmanager通过装饰器方式就可实现

from contextlib import contextmanager

@contextmanager
def context_func():
    yield "context"  # 使用yield返回一个对象赋值给as后的变量


if __name__ == "__main__":
    with context_func() as var:
        print(var)
posted @ 2022-06-23 20:11  店里最会撒谎白玉汤  阅读(47)  评论(0)    收藏  举报