Python by Structure:上下文管理器与 with 语句

发布: (2025年12月7日 GMT+8 15:03)
4 min read
原文: Dev.to

Source: Dev.to

手动文件处理的问题

Timothy 在调试一个文件处理脚本时,Margaret 在日志中注意到一个 “打开的文件过多” 错误。

def process_config():
    f = open('config.txt', 'r', encoding='utf-8')
    data = f.read()
    # Process data...
    if 'error' in data:
        return None  # Oops – file never closed!
    f.close()
    return data

如果走到早期的 return,文件将保持打开状态。依赖垃圾回收不可靠,且可能耗尽系统资源。

传统解决方案:try/finally

def process_config():
    f = open('config.txt', 'r', encoding='utf-8')
    try:
        data = f.read()
        if 'error' in data:
            return None
        return data
    finally:
        f.close()

finally 块保证无论函数如何退出,f.close() 都会执行,但额外的样板代码令人烦恼。

with 语句

Python 的 with 语句自动完成清理工作:

def process_config():
    with open('config.txt', 'r', encoding='utf-8') as f:
        data = f.read()
        if 'error' in data:
            return None
        return data
# File is automatically closed here

with 块有明确的入口点(打开文件)和退出点(缩进块结束)。Python 保证当块被离开时文件会被关闭,无论是正常执行、提前 return,还是抛出异常。

with 的工作原理:上下文管理器

with 语句中使用的对象必须实现两个特殊方法:

  • __enter__(self): 在块开始时调用;其返回值绑定到 as 后面的变量。
  • __exit__(self, exc_type, exc_val, exc_tb): 在块结束时调用;如果抛出了异常,会收到异常信息。

简单的上下文管理器示例

class FileLogger:
    def __init__(self, filename):
        self.filename = filename
        self.file = None

    def __enter__(self):
        print(f"Opening {self.filename}")
        self.file = open(self.filename, 'w', encoding='utf-8')
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"Closing {self.filename}")
        if self.file:
            self.file.close()
        return False  # Do not suppress exceptions

# Usage
with FileLogger('output.log') as log:
    log.write('Starting process\n')
    log.write('Processing data\n')
# File automatically closed here

输出

Opening output.log
Closing output.log

__exit__ 中的异常处理

如果在 with 块内部出现异常,Python 会把异常细节传递给 __exit__。该方法可以通过返回 True 来决定是否抑制异常。

class ErrorLogger:
    def __enter__(self):
        print("Entering context")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            print(f"Exception occurred: {exc_type.__name__}: {exc_val}")
            return True   # Suppress the exception
        print("Exiting normally")
        return False

with ErrorLogger():
    print("Working...")
    raise ValueError("Something went wrong!")
print("Continuing after exception")

输出

Entering context
Working...
Exception occurred: ValueError: Something went wrong!
Continuing after exception

使用多个 with 语句

可以嵌套 with 语句,或在单行中组合使用(Python 3.1+)。

# Nested
with open('input.txt', 'r', encoding='utf-8') as infile:
    with open('output.txt', 'w', encoding='utf-8') as outfile:
        outfile.write(infile.read())

# Combined
with open('input.txt', 'r', encoding='utf-8') as infile, \
     open('output.txt', 'w', encoding='utf-8') as outfile:
    outfile.write(infile.read())

两个文件都保证被关闭,关闭顺序与获取顺序相反(后打开的先关闭)。

上下文管理器的常见用例

  • 文件操作 – 保证关闭
  • 数据库连接 – 保证提交/回滚
  • 锁和信号量 – 保证释放
  • 网络连接 – 保证断开
  • 临时状态更改 – 保证恢复
  • 任何需要清理的资源

示例:线程锁

import threading

lock = threading.Lock()

# Without context manager – risky
lock.acquire()
try:
    # Critical section
    pass
finally:
    lock.release()

# With context manager – safe
with lock:
    # Critical section
    pass

即使在关键区块内部抛出异常,锁也会自动释放。

Back to Blog

相关文章

阅读更多 »

扁平化嵌套列表

大家好!👋 我知道我最近有点沉默。上周我真的得了相当严重的流感,完全把我击倒了。🤒 这就是为什么我 mis...