Python中with的作用和使用解读

 更新时间:2025年07月18日 08:57:59   作者:Tipriest_  
Python的with语句通过上下文管理器协议(__enter__和__exit__方法),简化资源管理,确保文件、数据库等资源在使用后自动释放,避免泄露,支持自定义实现,提升代码简洁性与可读性

在这里我们来详细解释一下Python中非常重要的 with 语句。

我会从 “为什么需要它” 开始,然后讲解 “它是什么以及如何使用”,最后深入到 “它的工作原理”“如何自定义”

1. 为什么需要with语句?(The Problem)

在编程中,我们经常会使用一些需要“获取”和“释放”的资源,比如:

  • 文件操作:打开文件后,必须记得关闭它。
  • 数据库连接:建立连接后,必须记得关闭连接。
  • 线程锁:获取锁之后,必须记得释放它。

如果我们忘记释放这些资源,可能会导致严重的问题,比如:

  • 文件句柄耗尽,无法再打开新文件。
  • 数据库连接池被占满,应用无法再连接数据库。
  • 线程死锁,程序卡住。

让我们看一个没有 with 的文件操作例子:

不安全的写法:

f = open('my_file.txt', 'w')
f.write('hello world')
# 如果在 write 和 close 之间发生错误,close() 将永远不会被执行!
f.close()

这个写法非常危险。如果在 f.write() 时发生异常(例如磁盘满了),程序会崩溃,f.close() 就不会被调用,文件资源就泄露了。

安全的、但繁琐的写法 (使用 try...finally):

为了确保资源一定被释放,我们通常使用 try...finally 结构:

f = None # 在 try 外面初始化,确保 finally 中可以访问
try:
    f = open('my_file.txt', 'w')
    f.write('hello world')
    # ... 其他可能出错的操作 ...
finally:
    if f:
        f.close()

这个写法是安全的,因为无论 try 块中是否发生异常,finally 块中的代码都保证会被执行。但是,它看起来很冗长,代码结构也不够优雅。

with 语句就是为了解决这个问题而生的,它能让我们用更简洁、更安全的方式来管理资源。

2.with语句是什么以及如何使用?(The Solution)

with 语句是一种上下文管理的语法糖(Syntactic Sugar)。它极大地简化了上面 try...finally 的写法。

基本语法:

with expression as variable:
    # 在这个代码块中,资源是可用的
    # ... do something with variable ...

# 离开 with 代码块后,资源会自动被清理

使用 with 重写文件操作:

with open('my_file.txt', 'w') as f:
    f.write('hello world')
    # 在这里可以进行各种文件操作
    # 比如 f.read(), f.writelines() 等

# 当代码执行离开这个 with 块时(无论是正常结束还是发生异常),
# Python 会自动调用 f.close(),我们完全不需要操心。

对比一下:

  • try...finally 版本:5-6 行代码,结构复杂。
  • with 版本:2 行代码,逻辑清晰,意图明确(“在处理这个文件的上下文中,做这些事”)。

with 语句的核心优势是:无论 with 块内部发生什么(即使是异常),它都保证能执行资源的“清理”操作

3.with的工作原理:上下文管理器协议 (The Magic Behind)

with 语句之所以能自动管理资源,是因为它遵循了上下文管理器协议(Context Manager Protocol)

一个对象只要实现了下面这两个特殊方法,它就是一个上下文管理器:

__enter__(self)

  • 何时调用:当进入 with 语句块时,该方法被调用。
  • 作用:负责“获取”资源或进行初始化设置。
  • 返回值:这个方法的返回值会赋给 as 后面的变量(如果 as 存在的话)。如果你不需要 as 变量,这个方法可以不返回任何东西。

__exit__(self, exc_type, exc_value, traceback)

  • 何时调用:当离开 with 语句块时(无论是正常退出还是因为异常退出),该方法被调用。
  • 作用:负责“释放”资源或执行清理操作(比如 f.close())。

参数

  • exc_type: 异常的类型(如果没发生异常,则为 None)。
  • exc_value: 异常的值(如果没发生异常,则为 None)。
  • traceback: 异常的追溯信息(如果没发生异常,则为 None)。

返回值

  • 如果 __exit__ 方法返回 True,表示它已经处理了这个异常,异常会被“吞掉”(suppress),程序不会向外抛出。
  • 如果它返回 FalseNone(默认情况),任何发生的异常都会在 __exit__ 执行完毕后被重新抛出。

所以,with open(...) as f: 这段代码大致等同于下面的伪代码:

# 1. 创建上下文管理器对象
manager = open('my_file.txt', 'w')

# 2. 调用 __enter__ 方法,返回值赋给 f
f = manager.__enter__()

# 3. 执行 with 块中的代码
try:
    f.write('hello world')
finally:
    # 4. 无论如何,都调用 __exit__ 方法进行清理
    # (这里简单展示,实际会传递异常信息)
    manager.__exit__(None, None, None)

4. 如何创建自己的上下文管理器?

了解了原理,我们就可以创建自己的上下文管理器。有两种主要方式:

方式一:基于类的实现

我们可以写一个类,并实现 __enter____exit__ 方法。

示例:一个简单的计时器

import time

class Timer:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print(f"计时器 '{self.name}' 开始...")
        self.start_time = time.time()
        # 这个类本身就是资源,所以返回 self
        return self 

    def __exit__(self, exc_type, exc_value, traceback):
        self.end_time = time.time()
        duration = self.end_time - self.start_time
        print(f"计时器 '{self.name}' 结束,耗时: {duration:.4f} 秒")
        # 如果有异常,这里可以记录日志
        if exc_type:
            print(f"在 '{self.name}' 中发生了异常: {exc_value}")
        # 返回 False 或 None,让异常正常抛出
        return False

# 使用自定义的 Timer
with Timer("数据处理") as t:
    print("正在处理数据...")
    time.sleep(2)
    print("数据处理完成。")

print("-" * 20)

with Timer("有问题的操作") as t:
    print("准备执行一个会出错的操作...")
    time.sleep(1)
    result = 1 / 0  # 这里会产生一个 ZeroDivisionError
    print("这行代码不会被执行")

输出:

计时器 '数据处理' 开始...
正在处理数据...
数据处理完成。
计时器 '数据处理' 结束,耗时: 2.0021 秒
--------------------
计时器 '有问题的操作' 开始...
准备执行一个会出错的操作...
计时器 '有问题的操作' 结束,耗时: 1.0011 秒
在 '有问题的操作' 中发生了异常: division by zero
Traceback (most recent call last):
  File "...", line 36, in <module>
    result = 1 / 0  # 这里会产生一个 ZeroDivisionError
ZeroDivisionError: division by zero

可以看到,即使发生了异常,__exit__ 方法仍然被调用,成功打印了耗时和异常信息。

方式二:基于生成器的实现(使用contextlib模块)

对于简单的上下文管理器,每次都写一个类有点麻烦。

Python 的 contextlib 模块提供了一个 @contextmanager 装饰器,可以让我们用更简洁的方式实现。

import time
from contextlib import contextmanager

@contextmanager
def timer(name):
    print(f"计时器 '{name}' 开始...")
    start_time = time.time()
    
    # yield 之前的部分,相当于 __enter__
    # yield 的值会成为 as 后面的变量(如果没有 yield 值,则为 None)
    try:
        yield
    finally:
        # yield 之后的部分,相当于 __exit__
        end_time = time.time()
        duration = end_time - start_time
        print(f"计时器 '{name}' 结束,耗时: {duration:.4f} 秒")

# 使用方法完全一样
with timer("数据处理_v2"):
    print("正在处理数据...")
    time.sleep(2)
    print("数据处理完成。")

这种方式更加 Pythonic,代码也更紧凑。try...yield...finally 结构完美地对应了“进入-执行-清理”的模式。

总结

  • 用途with 语句用于自动管理资源,确保资源在使用完毕后(无论是否发生异常)都能被正确清理。
  • 优点:代码更简洁、更安全、更具可读性,避免了冗长的 try...finally 结构和资源泄露的风险。
  • 原理:依赖于上下文管理器协议,即对象需实现 __enter__()__exit__() 两个方法。
  • 自定义:你可以通过编写类或使用 contextlib.contextmanager 装饰器来创建自己的上下文管理器,封装任何需要“设置-清理”逻辑的场景。

在现代 Python 编程中,只要遇到需要获取和释放资源的场景,都应该优先考虑使用 with 语句。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 简单解决Python文件中文编码问题

    简单解决Python文件中文编码问题

    这篇文章主要介绍了简单解决Python文件中文编码问题的相关资料,需要的朋友可以参考下
    2015-11-11
  • 详解Python同时写入多个文件的5种方法

    详解Python同时写入多个文件的5种方法

    在实际开发中,有同学经常问田辛老师需要将数据同时写入多个文件的场景,Python提供了多种高效且安全的方法来实现这一需求,下面小编就来和大家简单讲讲吧
    2025-05-05
  • pyqt6的本地环境部署(conda和vscode环境)

    pyqt6的本地环境部署(conda和vscode环境)

    本文主要介绍了pyqt6的本地环境部署(conda和vscode环境),文中通过示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-05-05
  • pytorch中forwod函数在父类中的调用方式解读

    pytorch中forwod函数在父类中的调用方式解读

    这篇文章主要介绍了pytorch中forwod函数在父类中的调用方式解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • Python matplotlib绘制实时数据动画

    Python matplotlib绘制实时数据动画

    Matplotlib作为Python的2D绘图库,它以各种硬拷贝格式和跨平台的交互式环境生成出版质量级别的图形。本文将利用Matplotlib库绘制实时数据动画,感兴趣的可以了解一下
    2022-03-03
  • Python计算标准差之numpy.std和torch.std的区别

    Python计算标准差之numpy.std和torch.std的区别

    Torch自称为神经网络中的numpy,它会将torch产生的tensor放在GPU中加速运算,就像numpy会把array放在CPU中加速运算,下面这篇文章主要给大家介绍了关于Python Numpy计算标准差之numpy.std和torch.std区别的相关资料,需要的朋友可以参考下
    2022-08-08
  • Python实现交通数据可视化的示例代码

    Python实现交通数据可视化的示例代码

    本文主要分享了Python交通数据分析与可视化的实战!其中主要是使用TransBigData库快速高效地处理、分析、挖掘出租车GPS数据,感兴趣的可以了解一下
    2023-04-04
  • python  Django中的apps.py的目的是什么

    python Django中的apps.py的目的是什么

    这篇文章主要介绍了python Django中的apps.py的目的是什么,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2018-10-10
  • python贪吃蛇核心功能实现下

    python贪吃蛇核心功能实现下

    我想大家都玩过诺基亚上面的贪吃蛇吧,这篇文章将带你一步步用python语言实现一个snake小游戏,文中的示例代码讲解详细,感兴趣的可以了解一下
    2022-09-09
  • Python字符串格式化输出代码实例

    Python字符串格式化输出代码实例

    这篇文章主要介绍了Python字符串格式化输出代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11

最新评论