with 关键字

with 关键字

概述

Python 中的 with 语句用于上下文管理器(context manager)定义的方法包装块的执行,它允许将常见的 try...except...finally 使用模式封装起来以方便重用。with 语句确保在使用资源的代码完成运行时能够”清理”资源,即使抛出异常也是如此。

学习目标

通过本章学习,你将掌握:

  • with 语句的基本语法和执行原理
  • 上下文管理器的概念和实现
  • 自定义上下文管理器的方法
  • with 语句的实际应用场景
  • 异常处理和资源管理的最佳实践

前置知识

  • Python 基础语法
  • 异常处理机制
  • 类和对象的基本概念
  • 文件操作基础

详细内容

基本语法

with EXPRESSION [as TARGET]:
    SUITE

执行过程

with 语句的执行过程如下:

  1. 评估上下文表达式以获得上下文管理器
  2. 上下文管理器的 __enter__() 被加载以备后用
  3. 上下文管理器的 __exit__() 被加载以备后用
  4. 上下文管理器的 __enter__() 方法被调用
  5. 如果 TARGET 包含在 with 语句中,则将 __enter__() 的返回值赋值给它
  6. SUITE 被执行
  7. 上下文管理器的 __exit__() 方法被调用

基本使用示例

文件操作

## 传统方式
file = open('example.txt', 'r')
try:
    content = file.read()
    print(content)
finally:
    file.close()

## 使用 with 语句
with open('example.txt', 'r') as file:
    content = file.read()
    print(content)
## 文件会自动关闭,即使发生异常

多个资源管理

## 同时管理多个文件
with open('input.txt', 'r') as infile, open('output.txt', 'w') as outfile:
    data = infile.read()
    outfile.write(data.upper())
## 两个文件都会自动关闭

自定义上下文管理器

基于类的上下文管理器

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        print(f"打开文件: {self.filename}")
        self.file = open(self.filename, self.mode)
        return self.file
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"关闭文件: {self.filename}")
        if self.file:
            self.file.close()
        
#        # 返回 False 表示不抑制异常
#        # 返回 True 表示抑制异常
        if exc_type is not None:
            print(f"发生异常: {exc_type.__name__}: {exc_val}")
        return False

## 使用自定义上下文管理器
with FileManager('test.txt', 'w') as f:
    f.write('Hello, World!')
#    # 如果这里发生异常,文件仍会被正确关闭

数据库连接管理器

class DatabaseConnection:
    def __init__(self, host, port, database):
        self.host = host
        self.port = port
        self.database = database
        self.connection = None
    
    def __enter__(self):
        print(f"连接到数据库: {self.host}:{self.port}/{self.database}")
#        # 这里应该是实际的数据库连接代码
        self.connection = f"connection_to_{self.database}"
        return self.connection
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("关闭数据库连接")
        if self.connection:
#            # 这里应该是实际的连接关闭代码
            self.connection = None
        
        if exc_type is not None:
            print(f"数据库操作异常: {exc_type.__name__}")
#            # 可以在这里进行事务回滚
            return False  # 不抑制异常
        else:
#            # 可以在这里进行事务提交
            print("数据库操作成功")
            return False

## 使用数据库连接管理器
with DatabaseConnection('localhost', 5432, 'mydb') as conn:
    print(f"使用连接: {conn}")
#    # 执行数据库操作

使用 contextlib 模块

@contextmanager 装饰器

from contextlib import contextmanager
import time

@contextmanager
def timer():
    start_time = time.time()
    print("开始计时")
    try:
        yield start_time
    finally:
        end_time = time.time()
        print(f"执行时间: {end_time - start_time:.2f} 秒")

## 使用计时器
with timer() as start:
    time.sleep(1)
    print("执行一些耗时操作")

临时目录管理

from contextlib import contextmanager
import tempfile
import shutil
import os

@contextmanager
def temporary_directory():
    temp_dir = tempfile.mkdtemp()
    print(f"创建临时目录: {temp_dir}")
    try:
        yield temp_dir
    finally:
        print(f"删除临时目录: {temp_dir}")
        shutil.rmtree(temp_dir)

## 使用临时目录
with temporary_directory() as temp_dir:
#    # 在临时目录中创建文件
    file_path = os.path.join(temp_dir, 'temp_file.txt')
    with open(file_path, 'w') as f:
        f.write('临时文件内容')
    print(f"临时文件创建在: {file_path}")
## 临时目录和文件会自动删除

异常处理

异常信息获取

class ExceptionHandler:
    def __enter__(self):
        print("进入上下文")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("退出上下文")
        if exc_type is not None:
            print(f"异常类型: {exc_type}")
            print(f"异常值: {exc_val}")
            print(f"异常回溯: {exc_tb}")
#            # 返回 True 抑制异常,返回 False 或 None 不抑制
            return False
        return False

## 测试异常处理
with ExceptionHandler():
    print("正常执行")
    raise ValueError("这是一个测试异常")
    print("这行不会执行")

异常抑制示例

class IgnoreException:
    def __init__(self, *exception_types):
        self.exception_types = exception_types
    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            if issubclass(exc_type, self.exception_types):
                print(f"忽略异常: {exc_type.__name__}: {exc_val}")
                return True  # 抑制异常
        return False

## 使用异常抑制
with IgnoreException(ValueError, TypeError):
    print("开始执行")
    raise ValueError("这个异常会被忽略")
    print("这行不会执行")

print("程序继续执行")

实际应用场景

1. 线程锁管理

import threading
from contextlib import contextmanager

class ThreadSafeLock:
    def __init__(self):
        self._lock = threading.Lock()
    
    def __enter__(self):
        print("获取锁")
        self._lock.acquire()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("释放锁")
        self._lock.release()
        return False

## 使用线程锁
lock = ThreadSafeLock()
shared_resource = 0

def worker():
    global shared_resource
    with lock:
#        # 临界区代码
        temp = shared_resource
        temp += 1
        shared_resource = temp
        print(f"Worker updated resource to: {shared_resource}")

## 创建多个线程
threads = []
for i in range(3):
    t = threading.Thread(target=worker)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

2. 配置管理

from contextlib import contextmanager

class ConfigManager:
    def __init__(self):
        self.config = {'debug': False, 'log_level': 'INFO'}
    
    @contextmanager
    def temporary_config(self, **kwargs):
#        # 保存原始配置
        original_config = self.config.copy()
        
#        # 应用临时配置
        self.config.update(kwargs)
        print(f"应用临时配置: {kwargs}")
        
        try:
            yield self.config
        finally:
#            # 恢复原始配置
            self.config = original_config
            print("恢复原始配置")

## 使用配置管理器
config_manager = ConfigManager()
print(f"原始配置: {config_manager.config}")

with config_manager.temporary_config(debug=True, log_level='DEBUG'):
    print(f"临时配置: {config_manager.config}")
#    # 在这个块中使用调试配置

print(f"恢复后配置: {config_manager.config}")

3. 性能监控

from contextlib import contextmanager
import time
import psutil
import os

@contextmanager
def performance_monitor(operation_name):
#    # 记录开始状态
    start_time = time.time()
    process = psutil.Process(os.getpid())
    start_memory = process.memory_info().rss / 1024 / 1024  # MB
    
    print(f"开始监控操作: {operation_name}")
    print(f"初始内存使用: {start_memory:.2f} MB")
    
    try:
        yield
    finally:
#        # 记录结束状态
        end_time = time.time()
        end_memory = process.memory_info().rss / 1024 / 1024  # MB
        
        execution_time = end_time - start_time
        memory_diff = end_memory - start_memory
        
        print(f"操作完成: {operation_name}")
        print(f"执行时间: {execution_time:.2f} 秒")
        print(f"内存变化: {memory_diff:+.2f} MB")
        print(f"最终内存使用: {end_memory:.2f} MB")

## 使用性能监控
with performance_monitor("大数据处理"):
#    # 模拟一些耗时和内存密集的操作
    data = [i ** 2 for i in range(1000000)]
    time.sleep(0.5)
    result = sum(data)
    print(f"计算结果: {result}")

4. HTTP 请求会话管理

from contextlib import contextmanager
import requests

@contextmanager
def http_session(base_url, headers=None, timeout=30):
    session = requests.Session()
    
    if headers:
        session.headers.update(headers)
    
#    # 设置基础 URL
    session.base_url = base_url
    
    print(f"创建 HTTP 会话: {base_url}")
    
    try:
        yield session
    finally:
        session.close()
        print("关闭 HTTP 会话")

## 使用 HTTP 会话管理
headers = {
    'User-Agent': 'MyApp/1.0',
    'Accept': 'application/json'
}

with http_session('https://api.example.com', headers=headers) as session:
#    # 在这个块中使用会话进行多个请求
#    # response1 = session.get('/users')
#    # response2 = session.post('/data', json={'key': 'value'})
    print("执行 HTTP 请求...")

常见陷阱与最佳实践

1. exit 方法的返回值

class ExampleContext:
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            print(f"捕获到异常: {exc_val}")
#            # 返回 True 会抑制异常
#            # 返回 False 或 None 会传播异常
            return True  # 抑制异常
        return False

## 异常被抑制,程序继续执行
with ExampleContext():
    raise ValueError("测试异常")
    
print("程序继续执行")  # 这行会被执行

2. 嵌套上下文管理器

## 方法 1:嵌套 with 语句
with open('file1.txt', 'r') as f1:
    with open('file2.txt', 'w') as f2:
        data = f1.read()
        f2.write(data.upper())

## 方法 2:使用逗号分隔(推荐)
with open('file1.txt', 'r') as f1, open('file2.txt', 'w') as f2:
    data = f1.read()
    f2.write(data.upper())

## 方法 3:使用 ExitStack(复杂场景)
from contextlib import ExitStack

with ExitStack() as stack:
    f1 = stack.enter_context(open('file1.txt', 'r'))
    f2 = stack.enter_context(open('file2.txt', 'w'))
    
    data = f1.read()
    f2.write(data.upper())

3. 资源清理的重要性

## 错误示例:没有正确清理资源
class BadResource:
    def __init__(self, name):
        self.name = name
        self.resource = f"resource_{name}"
    
    def __enter__(self):
        print(f"获取资源: {self.resource}")
        return self.resource
    
    def __exit__(self, exc_type, exc_val, exc_tb):
#        # 忘记清理资源
        pass

## 正确示例:确保资源清理
class GoodResource:
    def __init__(self, name):
        self.name = name
        self.resource = None
    
    def __enter__(self):
        self.resource = f"resource_{self.name}"
        print(f"获取资源: {self.resource}")
        return self.resource
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.resource:
            print(f"清理资源: {self.resource}")
            self.resource = None
        return False

相关函数与模块

内置函数

  • open() - 文件操作的内置上下文管理器
  • iter() - 某些迭代器也支持上下文管理协议

标准库模块

  • contextlib - 上下文管理工具
    • @contextmanager - 装饰器,简化上下文管理器创建
    • ExitStack - 动态管理多个上下文管理器
    • closing() - 为有 close() 方法的对象提供上下文管理
    • suppress() - 抑制指定异常
  • threading - 线程锁等同步原语都支持上下文管理
  • multiprocessing - 进程锁等也支持上下文管理

第三方库

  • requests - HTTP 会话管理
  • sqlite3 - 数据库连接和事务管理
  • psycopg2 - PostgreSQL 数据库连接管理

扩展阅读

相关标签

Python 上下文管理器 资源管理 异常处理 with 语句 contextlib 文件操作 内存管理

讨论与反馈

欢迎在下方留言讨论,分享你的学习心得或提出问题。评论基于GitHub Issues,需要GitHub账号。