0

0

谈谈你对Python上下文管理器的理解(with语句)。

幻影之瞳

幻影之瞳

发布时间:2025-09-04 15:46:30

|

388人浏览过

|

来源于php中文网

原创

Python的with语句通过上下文管理器协议(__enter__和__exit__方法)实现资源的自动管理,确保其在使用后无论是否发生异常都能被正确释放。它简化了try...finally结构,广泛应用于文件操作、数据库事务、线程锁、临时状态更改和测试mock等场景,提升代码可读性与可靠性。

谈谈你对python上下文管理器的理解(with语句)。

谈到Python的上下文管理器,尤其是

with
语句,我个人觉得它就像是编程世界里的一位“管家”——你把需要照料的资源交给它,它就负责在恰当的时机打开、使用,并在无论发生什么(成功或失败)之后,都能妥善地帮你收拾干净。这大大简化了资源管理,让代码变得更健壮、更易读。

解决方案

简单来说,Python的

with
语句提供了一种优雅的方式来管理资源,确保它们在使用后总是能被正确地获取和释放。它的核心思想是“协议”:任何遵循上下文管理器协议的对象(即实现了
__enter__
__exit__
方法的对象)都可以配合
with
语句使用。当
with
语句执行时,它会首先调用对象的
__enter__
方法,这个方法通常负责资源的初始化或获取,并返回资源本身。然后,
with
语句块中的代码开始执行。无论这块代码是正常结束、还是因为异常而中断,
with
语句都会保证在最后调用对象的
__exit__
方法。
__exit__
方法负责资源的清理和释放工作,比如关闭文件、释放锁、回滚数据库事务等等。这种机制的巧妙之处在于,它将资源的获取和释放逻辑封装起来,使得开发者无需手动编写繁琐的
try...finally
块,从而有效避免了资源泄露和错误处理的复杂性。

为什么Python的with语句能让资源管理变得如此简单?

对我来说,

with
语句最吸引人的地方在于它的“自动化”和“确定性”。在没有
with
语句之前,处理文件、网络连接或者数据库会话这类资源时,我们通常需要一个
try...finally
结构来确保资源在操作完成后被关闭。比如打开一个文件:

f = open('my_file.txt', 'w')
try:
    f.write('Hello, world!')
except IOError as e:
    print(f"写入错误: {e}")
finally:
    f.close()

这段代码本身没问题,但如果有很多这样的操作,代码里就会充斥着大量的

try...finally
,读起来有点累,也容易出错——比如忘记
close()
with
语句的出现,彻底改变了这种局面。它把这些“样板代码”抽象化了。你只需要关注核心业务逻辑,资源何时打开、何时关闭,甚至在发生异常时如何处理,都由上下文管理器默默地帮你搞定。

立即学习Python免费学习笔记(深入)”;

with open('my_file.txt', 'w') as f:
    f.write('Hello, world!')
# 文件在这里自动关闭,即使上面发生异常

你看,代码是不是一下子清爽了很多?这种简化不仅仅是代码行数的减少,更重要的是,它降低了心智负担,让开发者可以更专注于业务逻辑的实现,而不是纠结于资源清理的细节。它提供了一个清晰的“入口”和“出口”,就像给资源管理画上了一个明确的边界,这种边界感,是提升代码可靠性的关键。

如何自定义一个Python上下文管理器?

要自定义上下文管理器,通常有两种主要方式,各有各的适用场景。

第一种,也是最基础的,是实现一个类。 这个类需要定义

__enter__
__exit__
这两个特殊方法。

__enter__(self)
:这个方法在
with
语句块开始执行时被调用。它应该返回一个值,这个值会被赋给
as
子句后面的变量(如果有的话)。如果不需要返回任何东西,也可以返回
self
。这里通常会进行资源的初始化或获取操作。

__exit__(self, exc_type, exc_val, exc_tb)
:这个方法在
with
语句块执行结束后被调用,无论是因为正常退出还是发生了异常。它的三个参数分别代表异常类型、异常值和追踪信息。如果
with
块中没有发生异常,这三个参数都会是
None
。如果
__exit__
方法返回
True
,那么它会“吞掉”发生的异常,即异常不会向外传播;如果返回
False
或不返回任何东西,异常会继续传播。

举个例子,我们来创建一个模拟数据库连接的上下文管理器:

class DatabaseConnection:
    def __init__(self, db_name):
        self.db_name = db_name
        self.connection = None

    def __enter__(self):
        print(f"正在打开数据库 '{self.db_name}'...")
        # 模拟数据库连接
        self.connection = f"连接到 {self.db_name}"
        return self.connection

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            print(f"数据库操作发生异常: {exc_val}")
            # 可以在这里执行回滚操作
            print("正在回滚事务...")
        print(f"正在关闭数据库 '{self.db_name}'...")
        # 模拟关闭连接
        self.connection = None
        # 如果我们不想让异常继续传播,可以返回True
        # return True 

使用它:

with DatabaseConnection('my_app_db') as db:
    print(f"成功获取连接: {db}")
    # 模拟一些数据库操作
    # raise ValueError("模拟一个数据库操作错误")
print("with语句块已结束。")

第二种,更Pythonic、更简洁的方式,是使用

contextlib.contextmanager
装饰器。 如果你的上下文管理器逻辑比较简单,只是围绕着一个函数或生成器,那么这种方式会非常方便。

GitHub Copilot
GitHub Copilot

GitHub AI编程工具,实时编程建议

下载
from contextlib import contextmanager

@contextmanager
def managed_resource(name):
    print(f"资源 '{name}' 正在被获取...")
    try:
        yield f"处理资源 {name}" # yield之前是__enter__,yield之后是__exit__
    finally:
        print(f"资源 '{name}' 正在被释放...")

with managed_resource('日志文件') as res:
    print(f"正在使用: {res}")
    # raise TypeError("模拟一个使用错误")

这里,

yield
语句将函数分成了两部分:
yield
之前的代码在
__enter__
时执行,
yield
之后(无论
with
块正常结束还是发生异常)在
__exit__
时执行。这种方式写起来更像普通的函数,对于很多场景来说,它比实现一个类要直观得多。我个人在处理临时资源或者一些不需要复杂状态管理的场景时,更倾向于使用
@contextmanager

Python上下文管理器在实际项目中都有哪些高级应用场景?

上下文管理器不仅仅是文件操作那么简单,它在很多高级和复杂的场景下都展现出强大的威力。

1. 数据库事务管理: 这是最典型的应用之一。在进行一系列数据库操作时,我们希望这些操作要么全部成功并提交,要么全部失败并回滚。

with
语句可以优雅地封装事务的开始、提交和回滚逻辑。

# 伪代码,实际会与具体的ORM或DB API结合
class Transaction:
    def __init__(self, connection):
        self.connection = connection

    def __enter__(self):
        self.connection.begin()
        return self.connection

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            self.connection.rollback()
            print("事务回滚。")
        else:
            self.connection.commit()
            print("事务提交。")

# 使用
# with Transaction(my_db_conn) as conn:
#     conn.execute("INSERT ...")
#     conn.execute("UPDATE ...")
#     # 如果这里发生异常,事务会自动回滚

这样,即使中间某个操作失败,数据库也能保持一致性,这在业务逻辑中至关重要。

2. 线程/进程锁管理: 在多线程或多进程编程中,为了避免数据竞争,我们经常需要使用锁(

threading.Lock
multiprocessing.Lock
)。手动管理锁的获取和释放(
acquire()
release()
)很容易忘记
release()
,导致死锁。
with
语句完美解决了这个问题。

import threading

lock = threading.Lock()

def critical_section():
    with lock: # 自动acquire,with块结束自动release
        print(f"{threading.current_thread().name} 正在访问共享资源...")
        # 模拟一些操作
        import time
        time.sleep(0.1)
    print(f"{threading.current_thread().name} 释放了锁。")

# 启动多个线程
# threads = [threading.Thread(target=critical_section, name=f"Thread-{i}") for i in range(5)]
# for t in threads:
#     t.start()
# for t in threads:
#     t.join()

这确保了锁总是在关键代码块执行完毕后被释放,极大地提升了并发编程的安全性。

3. 临时改变系统状态: 有时候,我们可能需要在代码执行期间临时改变一些全局或系统级别的设置,例如改变当前工作目录、修改环境变量、或者重定向标准输出/输入。

contextlib
模块中就有很多这样的工具

import os
from contextlib import chdir, redirect_stdout

# 临时改变工作目录
print(f"当前目录: {os.getcwd()}")
with chdir('/tmp'):
    print(f"进入 /tmp 后的目录: {os.getcwd()}")
    # 在这里执行一些与/tmp相关的操作
print(f"with块结束后目录: {os.getcwd()}")

# 临时重定向标准输出到文件
with open('output.log', 'w') as f:
    with redirect_stdout(f):
        print("这条信息会被写入 output.log")
    print("这条信息会打印到控制台")

这种模式使得局部状态的改变变得非常可控,避免了对全局状态的长期污染。

4. 测试中的Mocking: 在单元测试中,我们经常需要模拟(mock)外部依赖,比如数据库调用、API请求等。

unittest.mock.patch
就是一个强大的上下文管理器,它可以在
with
块内临时替换对象,并在块结束后恢复原状。

from unittest.mock import patch

def fetch_data_from_api():
    # 假设这里会真的调用一个外部API
    return "真实API数据"

def process_data():
    data = fetch_data_from_api()
    return f"处理后的数据: {data}"

# 在测试中
with patch('__main__.fetch_data_from_api', return_value="模拟API数据"):
    result = process_data()
    print(f"测试结果: {result}") # 输出:处理后的数据: 模拟API数据

# with块结束后,fetch_data_from_api恢复为原始实现
print(f"with块结束后真实数据: {process_data()}") # 输出:处理后的数据: 真实API数据

这让测试变得更加隔离和可重复,无需实际的外部依赖。

这些例子只是冰山一角,上下文管理器的核心理念——“封装资源的获取与释放,并保证其在任何情况下都能正确执行”——使其成为Python中一个非常强大且灵活的工具,值得我们深入理解和应用。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

502

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

166

2025.12.24

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

7

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

14

2026.01.21

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

356

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2078

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

348

2023.08.31

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

256

2023.09.05

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.3万人学习

Django 教程
Django 教程

共28课时 | 3.6万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号