pin_drop当前位置:知识文库 ❯ 图文

Python上下文管理器 - with语句

一、with语句概述

with 语句是Python中用于简化资源管理的语法结构。它通过上下文管理器(Context Manager)协议,确保资源在使用完毕后能够被正确地释放,即使在发生异常的情况下也是如此。

最常见的应用场景包括:文件操作、网络连接、数据库连接、锁的获取与释放等。使用 with 语句可以避免忘记关闭资源而导致的内存泄漏或文件描述符耗尽等问题。


二、with语句的语法

with 语句的基本语法非常简单:

代码示例

# 基本语法
with expression as variable:
    # 代码块
    do_something(variable)

# expression 返回一个上下文管理器对象
# as variable 是可选的,用于将上下文管理器的结果绑定到变量

with 语句的工作原理是:Python会在进入代码块时调用上下文管理器的 __enter__() 方法,在退出代码块时调用 __exit__() 方法。无论代码块是正常结束还是因异常退出,__exit__() 都会被执行。


三、with语句的基本用法

文件操作中的with语句

最经典的 with 语句用法是文件操作。对比传统写法:

代码示例

# 传统写法(不推荐)
f = open("test.txt", "r", encoding="utf-8")
try:
    content = f.read()
    print(content)
finally:
    f.close()

# 使用with语句(推荐)
with open("test.txt", "r", encoding="utf-8") as f:
    content = f.read()
    print(content)
# with语句会自动调用f.close(),无需手动关闭

写入文件示例

代码示例

# 使用with语句写入文件
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Hello, World!\n")
    f.write("这是with语句的写入示例\n")

# 读取刚才写入的内容
with open("output.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.strip())

异常安全演示

代码示例

# with语句在发生异常时也会自动关闭文件
with open("data.txt", "w", encoding="utf-8") as f:
    f.write("第一行数据\n")
    f.write("第二行数据\n")
    raise ValueError("模拟异常")  # 即使抛出异常,文件也会被正确关闭

print("文件已自动关闭")

四、自定义上下文管理器

我们可以通过实现 __enter____exit__ 方法来自定义上下文管理器:

代码示例

class MyContextManager:
    """自定义上下文管理器示例"""
    
    def __init__(self, name):
        self.name = name
    
    def __enter__(self):
        print(f"进入上下文: {self.name}")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"退出上下文: {self.name}")
        # 返回True表示异常已处理,不会继续向外传播
        # 返回False或None表示异常需要继续向外传播
        if exc_type is not None:
            print(f"捕获到异常: {exc_type.__name__}: {exc_val}")
        return False

# 使用自定义上下文管理器
with MyContextManager("测试上下文") as manager:
    print(f"在上下文中操作: {manager.name}")

print("---" * 20)

# 测试异常处理
with MyContextManager("异常测试") as manager:
    print("准备抛出异常...")
    raise RuntimeError("模拟错误")

数据库连接管理示例

代码示例

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 = {"db": self.db_name, "status": "connected"}
        return self.connection
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"正在关闭数据库连接: {self.db_name}")
        self.connection["status"] = "closed"
        # 如果发生异常,可以选择回滚事务
        if exc_type is not None:
            print("事务回滚中...")
        return False

# 使用数据库连接管理器
with DatabaseConnection("my_database") as conn:
    print(f"连接状态: {conn['status']}")
    # 执行数据库操作...
    print("执行查询操作...")

五、contextmanager装饰器

使用 @contextmanager 装饰器可以更简洁地创建上下文管理器。它来自于 contextlib 模块:

代码示例

from contextlib import contextmanager

@contextmanager
def file_manager(filename, mode):
    """使用contextmanager装饰器创建文件管理器"""
    print(f"打开文件: {filename}")
    f = open(filename, mode, encoding="utf-8")
    try:
        yield f  # yield之前的代码相当于__enter__
    finally:
        print(f"关闭文件: {filename}")
        f.close()  # yield之后的代码相当于__exit__

# 使用装饰器创建的文件管理器
with file_manager("test.txt", "w") as f:
    f.write("使用contextmanager创建的上下文管理器\n")

计时器示例

代码示例

from contextlib import contextmanager
import time

@contextmanager
def timer(description="操作"):
    """使用contextmanager创建计时器"""
    start = time.time()
    try:
        yield
    finally:
        elapsed = time.time() - start
        print(f"{description}耗时: {elapsed:.4f}秒")

# 使用计时器
with timer("数据处理"):
    # 模拟耗时操作
    time.sleep(0.5)
    total = sum(range(1000000))
    print(f"计算结果: {total}")

六、多个with语句的嵌套与合并

嵌套写法

代码示例

# 传统的嵌套写法
with open("input.txt", "r", encoding="utf-8") as infile:
    with open("output.txt", "w", encoding="utf-8") as outfile:
        content = infile.read()
        outfile.write(content.upper())
        print("文件复制完成")

合并写法(Python 2.7+ / 3.1+)

代码示例

# 合并写法(更简洁)
with open("input.txt", "r", encoding="utf-8") as infile, \
     open("output.txt", "w", encoding="utf-8") as outfile:
    content = infile.read()
    outfile.write(content.upper())
    print("文件复制完成")

小贴士

从Python 3.1开始,with语句支持同时管理多个上下文管理器,用逗号分隔即可。这比嵌套写法更加简洁,也更容易阅读。当上下文管理器超过2个时,建议使用括号包裹以保持良好的代码格式。


七、注意事项

注意1:with语句只能在支持上下文管理器的对象上使用。不是所有对象都支持 with 语句,对象必须实现 __enter____exit__ 方法。

注意2:在 with 代码块中 return 或 raise 时,__exit__ 方法仍然会被调用。但如果使用 os._exit() 直接终止进程,则不会触发清理操作。

注意3:with 语句的 as 部分是可选的。有些上下文管理器(如 threading.Lock)不需要绑定变量,直接使用 with lock: 即可。

注意4:自定义上下文管理器时,__exit__ 方法的返回值决定了异常是否会被抑制。返回 True 会阻止异常继续向外传播,返回 FalseNone 则会让异常继续传播。


八、小结

  • 自动资源管理:with 语句通过上下文管理器协议,自动处理资源的获取与释放,避免手动管理带来的疏漏。

  • 异常安全:即使在代码块中发生异常,with 语句也能确保 __exit__ 方法被执行,保证资源被正确释放。

  • 两种实现方式:可以通过类实现 __enter__/__exit__ 方法,也可以使用 @contextmanager 装饰器配合 yield 语句来创建上下文管理器。

  • 多上下文合并:Python 3.1+ 支持在单个 with 语句中同时管理多个上下文管理器,用逗号分隔即可。


九、练习题

练习1

编写程序,使用 with 语句同时打开两个文件,将第一个文件的内容复制到第二个文件中,并统计复制的行数。

代码示例

# 提示:使用合并的with语句
with open("source.txt", "r", encoding="utf-8") as src, \
     open("dest.txt", "w", encoding="utf-8") as dst:
    # 你的代码...

练习2

编写一个自定义上下文管理器 TempFile,在 __enter__ 时创建临时文件并返回文件对象,在 __exit__ 时自动删除该临时文件。

练习3

使用 @contextmanager 装饰器创建一个日志记录器上下文管理器,进入时记录开始时间,退出时记录结束时间和执行时长。

常见问题

with语句和try-finally有什么区别?

with语句本质上是try-finally的语法糖,但更加简洁优雅。with语句将资源的获取和释放逻辑封装在上下文管理器中,代码更易于复用和维护。try-finally需要每次手动编写释放代码,容易遗漏。

with语句中as部分可以省略吗?

可以。as部分是可选的,当不需要访问上下文管理器返回的对象时可以省略。例如使用threading.Lock时:`with lock:` 即可,不需要绑定变量。

__exit__方法的三个参数分别是什么?

__exit__(self, exc_type, exc_val, exc_tb):exc_type是异常类型(没有异常时为None),exc_val是异常实例,exc_tb是异常的traceback对象。这三个参数可以帮助你判断是否需要处理异常。

contextmanager装饰器中yield的作用是什么?

yield将函数体分为两部分:yield之前的代码相当于__enter__方法,yield之后的代码相当于__exit__方法。yield返回的值会绑定到as后的变量上。try-finally结构确保即使发生异常,finally中的清理代码也会被执行。

标签: with语句 上下文管理器 contextmanager 资源管理 Python教程

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: Python seek()和tell()方法 - 文件指针定位与随机访问 下一篇: Python读写CSV完整教程

poll相关推荐