Python 中with语句详解和代码示例

Python 中的 with 语句是上下文管理器(Context Manager)最常见、最优雅的使用方式。它主要解决资源管理的问题(文件、网络连接、数据库连接、锁、临时目录等),能保证“无论是否发生异常,资源都会被正确释放”。

with 语句的核心价值(一句话总结)

替代传统的 try-finally 结构,让代码更简洁、更安全、更可读。

1. 最常见的用法 —— 文件操作

# 传统写法(容易忘记 close)
f = open('data.txt', 'r', encoding='utf-8')
try:
    content = f.read()
    print(content)
finally:
    f.close()          # 必须写在 finally 里

# 使用 with(推荐)
with open('data.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    print(content)
    # 这里可以随意写多行操作

# with 语句块结束后,f 自动被关闭(即使中间抛异常也一样)
print(f.closed)  # True

2. with 同时打开多个资源(Python 3.1+)

with open('input.txt') as fin, open('output.txt', 'w') as fout:
    for line in fin:
        fout.write(line.upper())

Python 3.3+ 更推荐这种写法(括号可省略):

with (
    open('input.txt', encoding='utf-8') as fin,
    open('output.log', 'a', encoding='utf-8') as log
):
    log.write("开始处理\n")
    for line in fin:
        log.write(f"处理: {line.strip()}\n")

3. 常见的内置上下文管理器

场景写法示例主要作用
文件with open(...) as f:自动关闭文件
线程锁with lock:自动获取/释放锁
decimal 精度控制with decimal.localcontext(prec=10):临时改变精度,退出后恢复
临时改变目录with tempfile.TemporaryDirectory() as tmp:用完自动删除临时目录
关闭连接with conn:(数据库连接对象)自动 commit / rollback + 关闭
抑制特定异常with contextlib.suppress(FileNotFoundError):忽略指定异常,不打印 traceback

4. 自己编写上下文管理器(两种主流方式)

方式一:基于类(最清晰、最常用)

class MyTimer:
    def __init__(self, name=""):
        self.name = name

    def __enter__(self):
        import time
        self.start = time.perf_counter()
        print(f"[{self.name}] 开始计时...")
        return self   # 可以返回 self 供 as 变量使用

    def __exit__(self, exc_type, exc_value, traceback):
        import time
        elapsed = time.perf_counter() - self.start
        print(f"[{self.name}] 结束,用时: {elapsed:.4f} 秒")
        # 返回 True 表示“吞掉”异常;返回 False 或 None 则异常继续向外抛
        return False


# 使用
with MyTimer("下载任务") as t:
    time.sleep(1.2)
    # 模拟一些工作

方式二:用 @contextmanager 装饰器(更简洁)

from contextlib import contextmanager
import time

@contextmanager
def timer(name=""):
    start = time.perf_counter()
    print(f"[{name}] 开始...")
    try:
        yield   # yield 之前的代码是 __enter__,之后的代码是 __exit__
    finally:
        elapsed = time.perf_counter() - start
        print(f"[{name}] 结束,用时: {elapsed:.3f}s")


# 使用方式和普通 with 完全一样
with timer("测试块"):
    time.sleep(0.8)
    print("正在干活...")

5. 常见面试/进阶问题点

问题答案要点
with 语句执行顺序是什么?1. 执行 __enter__()
2. 执行 with 代码块
3. 执行 __exit__()(无论是否异常)
__exit__ 返回 True 有什么效果?代表“异常已被处理”,外层不会再看到这个异常
可以用 with 写多层嵌套吗?可以,但建议用并列写法(Python 3.3+ 支持括号多上下文)
为什么数据库连接库都支持 with?因为实现了上下文管理协议,能自动管理事务和连接关闭
contextlib.ExitStack 有什么用?当你需要在运行时动态决定要管理的资源数量时使用(高级用法)

6. 总结口诀

  • 凡是需要“配对使用”的资源(打开就要关闭、加锁就要解锁、进入就要退出),优先考虑用 with
  • 自己写上下文管理器时,优先使用 @contextmanager,代码更简洁
  • 一定要记住:with 结束时一定会执行清理动作(除非进程被强杀)

希望这些例子和说明能帮你彻底搞懂 with 语句~
有哪部分还想再深入看看吗?(比如 ExitStack、多线程锁、asyncio 中的 async with 等)

文章已创建 3958

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部