视频链接:

不如语冰

https://space.bilibili.com/70431433?spm_id_from=333.1007.0.0

文章代码链接:

https://github.com/zyf-ngu/Qmatter

7上下文管理与with语句

上下文管理是一种用于管理资源(如文件、网络连接、锁等)的机制,它确保在代码块执行前后进行正确的资源分配和释放,即使代码块中发生了异常也是如此。 在Python中,上下文管理是通过with语句来实现的。

with语句允许我们定义一个执行上下文,在该上下文中,我们可以确保资源的获取和释放是自动完成的。 即在with语句中,我们只需要关注资源或对象本身的操作(这点和普通方法一致),系统自动释放资源和对象(这点在普通方法中需要通过try except和finally实现,即try实现功能,except处理异常,finally释放资源。)

上下文管理器的基本结构

上下文管理的基本结构由两个特殊方法组成:__enter____exit__。这两个方法定义在上下文管理器对象中。

 1. __enter__(self):

当进入with语句块时,会调用这个方法。该方法返回的资源是类吗?会被赋值给as后面的变量(如果有的话)。 通常在这个方法中获取资源(如打开文件)。    

2. __exit__(self, exc_type, exc_value, traceback)`:

当离开with`语句块时(无论是正常结束还是因为异常),都会调用这个方法。

参数exc_type, exc_value, traceback分别表示异常类型、异常值和调用栈。如果没有异常发生,这三个参数都是None。 如果该方法返回True,则表示异常已经被处理,不会继续向外传播;如果返回False,则异常会被重新抛出(如果没有异常,返回什么都可以)。

通常在这个方法中释放资源(如关闭文件)。

上下文管理器的种类

除了使用类定义上下文管理器,Python还提供了contextmanager装饰器(在contextlib模块中),可以快速创建基于生成器的上下文管理器。这种上下文管理器使用yield语句将函数分成两部分:yield之前的部分相当于__enter__,之后的部分相当于__exit__。

方式1:类实现(完整控制)

特点:显式定义__enter__和__exit__,拥有完整异常处理能力,支持状态管理

class FileManager:

    def __init__(self, filename):

        self.filename = filename

        self.file = None

    def __enter__(self):

        print("打开文件...")

        self.file = open(self.filename)

        return self.file  # 返回资源对象

    def __exit__(self, exc_type, exc_val, exc_tb):

        print("关闭文件...")

        if self.file:

            self.file.close()

        # 处理异常(返回True则阻止异常传播)

        if exc_type:

            print(f"发生异常: {exc_type}")

        return False  # 不屏蔽异常

# 使用示例

with FileManager("test.txt", "w") as f:

    f.write("Hello Context!")  # 即使发生异常,文件也会关闭

    # 触发异常测试: 1/0

print("操作完成")

f是返回的资源名称(可任意取),f.write是该资源自己拥有的方法。

方式2:生成器实现(简洁方案)

特点:使用contextlib.contextmanager装饰器,用yield分隔资源获取/释放,代码更简洁

例1:

from contextlib import contextmanager

@contextmanager

def file_manager(filename, mode):

    try:

        print("打开文件...")

        f = open(filename, mode)

        yield f  # 返回资源

    except Exception as e:

        print(f"发生异常: {type(e).__name__}")

    finally:

        print("关闭文件...")

        f.close()

# 使用示例

with file_manager("test.txt", "a") as f:

    f.write("\nAppend content")

    # 触发异常测试: int('abc')

# 传统方式(易出错)

file = open('data.txt')

try:

data = file.read() # 若此处抛出异常,

file.close() 可能不被执行!

finally:

file.close() # 必须手动确保关闭

装饰器实现的上下文管理与普通的 try...except...finally 语句相比,核心优势在于代码复用性、可读性和扩展性,尤其更符合 “关注点分离” 的设计原则。以下从具体场景对比两者的优势:

普通 try...except 需要在每次使用资源时重复编写 “获取 - 清理” 逻辑,而装饰器实现的上下文管理可以将这部分逻辑封装成通用工具,在多个地方复用。

普通 try...except 会将 “业务逻辑” 和 “资源管理逻辑” 混在一起,而上下文管理通过 with 语句清晰分离两者,让代码意图更明确。阅读代码时,无需关注 “连接如何创建 / 关闭”“异常如何处理”,只需聚焦 with 块内的核心业务逻辑,降低理解成本。

普通 try...except 依赖开发者手动编写 finally 块来释放资源,容易因疏忽导致资源泄漏(如忘记写 f.close());而上下文管理通过装饰器强制封装了清理逻辑,无论开发者是否记得,资源都会被自动释放。

上下文管理通过装饰器的 finally 块(或类实现的 __exit__)强制执行清理操作,从机制上避免了人为遗漏,尤其适合团队协作(确保所有人都遵循资源管理规范)。

装饰器实现的上下文管理可以轻松扩展,支持嵌套、组合等复杂场景,而普通 try...except 嵌套会导致代码臃肿(“金字塔问题”)。

维度

装饰器上下文管理

普通 try...except

代码复用

一次封装,多处复用

每次使用需重复编写资源管理逻辑

可读性

分离业务逻辑与资源管理,意图明确

逻辑混杂,需通读代码才能理解意图

资源安全

强制自动释放,避免人为失误

依赖手动编写 finally,易遗漏

复杂场景支持

简洁支持嵌套、组合等场景

嵌套层级深,代码臃肿

例2:

from contextlib import contextmanager

@contextmanagerdef session_scope():

    # 创建新的数据库会话(相当于 __enter__ 方法)

    session = SessionLocal()

    try:

        # 将会话传递给 with 代码块使用

        yield session  # 暂停执行,将会话对象传递给 with 块

        # 代码块无异常时提交事务(yield 之后的部分相当于 __exit__ 中的正常退出逻辑)

        session.commit()

    except:

        # 发生异常时回滚事务

        session.rollback()

        # 重新抛出异常

        raise

    finally:

        # 始终关闭会话释放连接(相当于 __exit__ 中的清理逻辑)

        session.close()

with语句用法

with 语句对资源的类型没有限制,只要上下文管理器的 __enter__ 方法(或装饰器中的 yield 语句)返回的是一个合法对象,就可以作为资源变量。这个对象可以是:

基础类型(如文件对象、整数);

容器类型(如列表、字典);

可调用对象(如函数、类实例方法);

自定义类实例等。

当资源变量是函数时,它的作用通常是在 with 块内提供与当前上下文相关的操作(如示例中打印临时目录信息),同时享受上下文管理器的资源自动管理能力

with 类名(参数)/函数名(参数) as 变量:

    代码块

# 等价于:

manager = 上下文管理器()

变量 = manager.__enter__()

try:

代码块

finally:

manager.__exit__(异常信息)

类名(参数)/函数名(参数)上面已经介绍了就是上下文管理器的2种方式,那么后面这个as 有什么作用呢?

as 关键字的使用场景

情况

是否使用 as

说明

需要访问管理器返回值

必须使用

当 __enter__() 返回的值需要在代码块中使用时

仅需资源管理

可省略

不需要使用管理器返回值时

当需要直接操作上下文管理器返回的资源对象时:

# 文件操作:需要文件对象进行读写

with open('test.txt', 'w') as file:  # as 接收 open() 返回的文件对象

file.write("Hello, World!")      # 操作文件对象

# 退出 with 后文件自动关闭

当只需确保资源清理,不需要操作管理器返回值时,比如锁操作:

# 锁操作:只需确保锁被释放,无需直接操作锁对象

import threading

lock = threading.Lock()

with lock:  # 不需要 as,因为不需要操作锁对象

    print("临界区代码执行中")# 退出 with 后锁自动释放

class Timer:

    def __enter__(self):

        self.start = time.time()

    def __exit__(self, *args):

        elapsed = time.time() - self.start

        print(f"代码块耗时: {elapsed:.2f}秒")

with Timer():  # 不需要 as,因为不关心返回值

    time.sleep(1.5)  # 模拟耗时操作

输出:

代码块耗时: 1.50秒

2. 异常处理更健壮

问题解决:在 __exit__() 中可统一处理异常(如回滚事务、日志记录)。

原理:__exit__() 接收异常信息,可通过返回 True 抑制异常。

class DatabaseTransaction:

def __enter__(self):

self.conn = sqlite3.connect('db.db')

return self.conn.cursor()

def __exit__(self, exc_type, exc_val, exc_tb):

if exc_type: # 发生异常时回滚

self.conn.rollback()

else: # 否则提交

self.conn.commit()

self.conn.close()

with DatabaseTransaction() as cursor:

cursor.execute("DELETE FROM users") # 若此处失败,自动回滚

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐