飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

上下文管理器

程序中大量资源需要成对操作:打开后必须关闭、加锁后必须解锁、连接后必须断开。手动管理这些资源极易因异常或逻辑分支导致泄漏。Python 的 with 语句通过上下文管理器协议,将资源获取与释放绑定为不可分割的原子操作,确保无论代码块正常结束还是异常抛出,清理逻辑始终执行。

with 语句的本质

with 语句的语法为 with expression [as variable]:,其中 expression 必须返回一个实现了上下文管理器协议的对象。进入 with 块时,协议启动资源;离开块时,协议关闭资源。

以文件为例,不使用 with 时,异常会跳过 close() 调用,导致文件句柄泄漏:

f = open('data.txt', 'r', encoding='utf-8')
try:
    data = f.read()
    # 若此处抛出异常,close() 永远不会执行
finally:
    f.close()

with 语句将上述 try-finally 结构压缩为一行,语义等价但更安全、更简洁:

with open('data.txt', 'r', encoding='utf-8') as f:
    data = f.read()
# 离开 with 块后,f.closed 为 True

as f 中的 f 接收的是上下文管理器 __enter__ 方法的返回值,不一定是管理器对象本身。对于文件对象,__enter__ 返回 self,因此 f 就是文件对象。

上下文管理器协议

任何类只要实现以下两个特殊方法,即可作为上下文管理器使用:

  • __enter__(self):进入 with 块时调用,负责资源初始化,返回值绑定到 as 后的变量。
  • __exit__(self, exc_type, exc_val, exc_tb):离开 with 块时调用,负责资源清理。三个参数描述块内发生的异常(若无异常则均为 None)。

__exit__ 的返回值决定异常是否继续传播:返回 True 表示异常已被处理,不再向上抛出;返回 False 或 None 则让异常正常传播。

class DatabaseConnection:
    def __enter__(self):
        self.connected = True
        print('连接数据库')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.connected = False
        print('关闭数据库')
        if exc_type is not None:
            print(f'块内发生异常:{exc_val}')
        return False   # 不吞掉异常,继续抛出

    def query(self, sql):
        if not self.connected:
            raise RuntimeError('未连接')
        print(f'执行:{sql}')

# 正常使用
with DatabaseConnection() as db:
    db.query('SELECT * FROM users')
# 离开块后自动关闭

# 异常场景
with DatabaseConnection() as db:
    db.query('SELECT *')
    raise ValueError('查询参数错误')   # __exit__ 仍会执行,异常继续抛出

若 __enter__ 自身抛出异常,with 块不会进入,__exit__ 也不会调用。若 __exit__ 需要处理特定异常类型,可检查 exc_type:

class SuppressZeroDivision:
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is ZeroDivisionError:
            print('捕获除零错误,静默处理')
            return True   # 吞掉异常
        return False

with SuppressZeroDivision():
    print(1 / 0)   # 不会崩溃,输出提示后正常继续
print('程序继续运行')

contextlib 模块

对于简单的资源管理,编写完整类显得笨重。contextlib 模块提供了更轻量的构建方式。

contextmanager 装饰器

用生成器函数配合 @contextmanager,yield 之前的代码等价于 __enter__,yield 的值绑定到 as 变量,yield 之后的代码等价于 __exit__(无论是否异常都会执行,因为被包装在 try-finally 中)。

from contextlib import contextmanager

@contextmanager
def managed_file(path, mode='r', encoding='utf-8'):
    f = open(path, mode, encoding=encoding)
    try:
        yield f
    finally:
        f.close()
        print(f'文件 {path} 已关闭')

with managed_file('test.txt', 'w') as f:
    f.write('hello')

生成器写法中,若 yield 之前的代码抛出异常,不会进入 with 块;若块内抛出异常,会在生成器中 yield 处重新抛出,因此 yield 通常应放在 try 块内,确保 finally 能执行清理。

其他实用工具

  • closing(thing):为仅实现了 close() 但没有 __enter__/__exit__ 的对象(如某些第三方库句柄)快速包裹上下文管理器。
from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('https://www.example.com')) as page:
    print(page.read(100))
  • suppress(*exceptions):静默忽略指定异常,等价于一个自动吞掉特定异常的上下文管理器。
from contextlib import suppress

with suppress(FileNotFoundError):
    os.remove('maybe_not_exist.tmp')   # 文件不存在也不报错
  • redirect_stdout(new_target) / redirect_stderr(new_target):临时重定向标准输出/错误流到文件或其他文件对象。
from contextlib import redirect_stdout
import io

f = io.StringIO()
with redirect_stdout(f):
    print('这条信息被捕获')
print(f.getvalue())   # '这条信息被捕获\n'
  • ExitStack:动态管理数量不确定的上下文管理器,在复杂场景下替代嵌套的 with。
from contextlib import ExitStack

filenames = ['a.txt', 'b.txt', 'c.txt']
with ExitStack() as stack:
    files = [stack.enter_context(open(name, 'r')) for name in filenames]
    # 所有文件在 ExitStack 退出时统一关闭
    contents = [f.read() for f in files]

多个上下文管理器

Python 支持在单个 with 语句中并列多个上下文管理器,用逗号分隔。执行顺序是:从左到右依次调用 __enter__,代码块结束后从右到左依次调用 __exit__。这种后进先出的顺序与嵌套 with 完全一致。

with open('src.txt', 'r', encoding='utf-8') as src, \
     open('dst.txt', 'w', encoding='utf-8') as dst:
    dst.write(src.read().upper())

等价的嵌套写法:

with open('src.txt', 'r', encoding='utf-8') as src:
    with open('dst.txt', 'w', encoding='utf-8') as dst:
        dst.write(src.read().upper())

并列写法更紧凑,适合管理多个同类资源。若某个 __enter__ 抛出异常,之前已成功进入的管理器会按相反顺序调用 __exit__,保证已获取的资源被正确释放。

实际应用场景

上下文管理器不仅用于文件,凡是需要成对操作的场景都适用:

  • 数据库事务:进入时开启事务,正常结束时提交,异常时回滚。
  • 线程/进程锁:with lock: 确保临界区结束后自动释放锁。
  • 临时环境变更:修改 sys.path、切换工作目录、设置环境变量,退出时恢复原状。
  • 性能计时:进入记录开始时间,退出计算并打印耗时。
import time
from contextlib import contextmanager

@contextmanager
def timer(name):
    start = time.perf_counter()
    yield
    elapsed = time.perf_counter() - start
    print(f'{name} 耗时: {elapsed:.4f} 秒')

with timer('数据处理'):
    sum(range(1000000))

总结

上下文管理器通过 __enter__ 和 __exit__ 两个方法,将资源生命周期与代码块作用域绑定。with 语句是调用方,@contextmanager 是轻量实现方,contextlib 中的 closing、suppress、redirect_stdout、ExitStack 等工具覆盖了常见变体。多个管理器并列时遵循后进先出原则。掌握这一协议,就能在文件、网络、锁、事务等任何资源场景中写出异常安全的代码。

上一页
文件读写
下一页
字符串格式化