with 语句和上下文管理器

with 语句的作用

with 是 Python 中处理资源(如文件、网络连接、数据库连接)的一种 语法糖,目的是为了确保使用完资源后,能自动清理资源,比如关闭文件、释放锁、断开连接等。

不使用 with 的资源处理方式(容易忘记关闭):

f = open('example.txt', 'r')
try:
    content = f.read()
finally:
    f.close()  # 必须手动关闭

使用 with 的方式(自动关闭):

with open('example.txt', 'r') as f:
    content = f.read()
# 离开 with 代码块后,f 会自动关闭

上下文管理器(Context Manager)

上下文管理器是一个实现了 __enter__()__exit__() 方法的对象。

这两个方法控制了:

  • __enter__():进入 with 块时调用,返回值绑定给 as 后面的变量。
  • __exit__():退出 with 块时调用,无论是否抛出异常,都会执行,通常用于清理操作。

with 语句的执行流程

with expression as variable:
    # do something

等价于如下代码:

mgr = expression          # mgr 是一个上下文管理器对象
var = mgr.__enter__()     # 调用 __enter__ 方法
try:
    # do something
finally:
    mgr.__exit__(exc_type, exc_value, traceback)  # 调用 __exit__ 方法

如果代码块中发生异常,异常信息会传给 __exit__ 的三个参数:

  • exc_type:异常类型(如 ValueError
  • exc_value:异常实例对象
  • traceback:异常的 traceback 信息

模拟一个文件打开的上下文管理器

我们来自己写一个类,模拟 Python 内置的 open() 行为:

class FileContextManager:
    def __init__(self, file_name, mode='r'):
        self.file_name = file_name  # 文件名
        self.mode = mode  # 读写模式
        self.file = None  # 文件对象,初始为 None

    def __enter__(self):
        print(f"打开文件: {self.file_name},模式: {self.mode}")
        # 打开文件并返回文件对象
        self.file = open(self.file_name, self.mode)
        return self.file  # 返回文件对象

    def __exit__(self, exc_type, exc_value, traceback):
        if self.file is not None:
            print("关闭文件")
            self.file.close()  # 确保文件被关闭
        
        if exc_type:  # 检查是否有异常
            print(f"发生异常: {exc_value}")
            return True  # 处理异常并防止抛出
        return False  # 允许异常向上抛出

使用方式:

# 使用自定义文件上下文管理器
try:
    with FileContextManager('example.txt', 'w') as file:  # 打开文件进行写入
        print("在上下文中写入一些内容")
        file.write("Hello, world!\n")
        file.write("这是第二行。\n")
        
    # 再读取之前写入的数据
    with FileContextManager('example.txt', 'r') as file:  # 打开文件进行读取
        print("读取文件内容:")
        content = file.read()
        print(content)  # 输出文件内容
except Exception as e:
    print(f"处理外部异常: {e}")
with 语句和上下文管理器

contextlib 模块简化上下文管理器

@contextmanager 装饰器

from contextlib import contextmanager

@contextmanager
def my_context():
    print("进入上下文")  # 相当于 __enter__
    try:
        yield "资源"     # yield 让出控制权,运行 with 块的代码
    except Exception as e:
        print(f"处理中发生异常: {e}")  # 你可以处理异常,也可以选择不写
        raise  # 重新抛出(可选)
    finally: 
        print("退出上下文")  # 相当于 __exit__,无论是否异常都会执行

# 使用:
with my_context() as res:
    print("处理中...", res)
    # raise ValueError("发生错误")  # 可加这一句测试异常处理
  • yield 前:等于 __enter__(),通常是资源申请、初始化的地方。
  • yield 后:等于 __exit__(),是资源清理、异常处理的地方。
  • 把 try-finally 包住 yield,是为了模仿底层的上下文管理器协议。

closing() 示例(对没有实现 __enter__ / __exit__ 的对象进行封装)

from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('http://example.com')) as page:
    for line in page:
        print(line)

Python 内置库 urllib 中的函数,用于打开一个 URL(网页),并返回一个类文件对象(类比 open() 打开的文件对象),它返回的对象具有 .read().close() 等方法,你可以像读文件一样读网页。因为 urlopen() 返回的对象没有实现 __enter__ / __exit__ 方法,所以不能直接用在 with 语句中(with urlopen('http://example.com') as page: # ❌ 会报错!)

closing() 是什么?

这是 contextlib 提供的一个工具函数,它的作用是:将“拥有 close() 方法但不是上下文管理器”的对象包装成一个可以用在 with 中的对象

它的底层实现类似于这样:

class closing:
    def __init__(self, thing):
        self.thing = thing

    def __enter__(self):
        return self.thing

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.thing.close()  # 离开 with 时自动调用 close()

也就是说:

with closing(urlopen(...)) as page:

就相当于:

page = urlopen(...)
try:
    ...
finally:
    page.close()  # 自动关闭连接,释放资源

常见的上下文管理器应用场景

场景说明
文件读写with open('file.txt') as f: 自动关闭文件
锁(多线程)with lock: 自动释放锁
数据库连接with connection.cursor() as cursor: 自动提交或回滚
临时修改状态比如临时更改目录,环境变量等

发布者:LJH,转发请注明出处:https://www.ljh.cool/43109.html

Like (0)
LJH的头像LJH
Previous 1天前
Next 15小时前

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注