前言

with 关键字是 Python 中用于上下文管理的重要语法结构,它能够自动管理资源的获取和释放,确保即使在发生异常的情况下也能正确清理资源。

一、with基本语法

with context_expression [as target]:
    # with 代码块
    statements

二、工作原理

with 语句背后的机制是上下文管理器协议,任何实现了 __enter__()__exit__() 方法的对象都可以作为上下文管理器。

执行流程:

  1. 执行 context_expression,获取上下文管理器
  2. 调用上下文管理器的 __enter__() 方法
  3. 如果有 as 子句,将 __enter__() 的返回值赋给 target
  4. 执行 with 代码块
  5. 无论代码块是否发生异常,都调用 __exit__() 方法

三、常见使用场景

文件操作(最常用)

# 传统方式需要手动关闭文件
file = open('example.txt', 'r')
try:
    content = file.read()
finally:
    file.close()

# 使用 with 语句,自动关闭文件
with open('example.txt', 'r') as file:
    content = file.read()
# 文件会自动关闭,无需手动调用 file.close()

数据库连接

import sqlite3

# 自动管理数据库连接
with sqlite3.connect('database.db') as conn:
    cursor = conn.cursor()
    cursor.execute('SELECT * FROM users')
    results = cursor.fetchall()
# 连接会自动关闭

线程锁

import threading

lock = threading.Lock()

# 自动获取和释放锁
with lock:
    # 临界区代码
    print("线程安全操作")
# 锁会自动释放

临时目录/文件

import tempfile

# 自动创建和清理临时目录
with tempfile.TemporaryDirectory() as temp_dir:
    print(f"临时目录: {temp_dir}")
    # 在临时目录中工作
# 临时目录会自动删除

四、自定义上下文管理器

基于类的实现

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 = f"连接到{self.db_name}"  # 模拟连接
        return self.connection
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"关闭数据库连接: {self.db_name}")
        self.connection = None
        # 如果返回 True,则异常不会被传播
        return False

# 使用自定义上下文管理器
with DatabaseConnection('my_db') as db:
    print(f"使用数据库: {db}")
    # 数据库操作

使用 contextlib 模块

from contextlib import contextmanager

@contextmanager
def timer():
    import time
    start = time.time()
    try:
        yield
    finally:
        end = time.time()
        print(f"执行时间: {end - start:.2f}秒")

# 使用上下文管理器装饰器
with timer():
    # 需要计时的代码
    time.sleep(2)

更复杂的例子

class FileHandler:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
        self.line_count = 0
    
    def __enter__(self):
        self.file = open(self.filename, self.mode)
        print(f"打开文件: {self.filename}")
        return self
    
    def read_line(self):
        line = self.file.readline()
        if line:
            self.line_count += 1
        return line
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
            print(f"关闭文件: {self.filename}")
            print(f"总共读取了 {self.line_count} 行")
        # 处理异常
        if exc_type is not None:
            print(f"发生异常: {exc_type}: {exc_val}")
        return False  # 不抑制异常

# 使用自定义文件处理器
with FileHandler('example.txt', 'r') as fh:
    while True:
        line = fh.read_line()
        if not line:
            break
        print(line.strip())

五、多个上下文管理器

可以同时使用多个上下文管理器:

# 同时打开多个文件
with open('file1.txt', 'r') as f1, open('file2.txt', 'r') as f2:
    content1 = f1.read()
    content2 = f2.read()
    # 两个文件都会自动关闭

# 或者写成多行形式
with open('file1.txt', 'r') as f1, \
     open('file2.txt', 'r') as f2, \
     open('output.txt', 'w') as f3:
    # 处理多个文件
    pass

六、高级用法

异常处理在 exit

class ErrorHandler:
    def __enter__(self):
        print("进入上下文")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            print(f"捕获到异常: {exc_type.__name__}: {exc_val}")
            # 返回 True 可以抑制异常
            return True
        print("正常退出上下文")
        return False

with ErrorHandler():
    print("正常操作")

with ErrorHandler():
    raise ValueError("这是一个错误")  # 异常会被捕获但不会传播

异步上下文管理器(Python 3.5+

import asyncio

class AsyncDatabaseConnection:
    async def __aenter__(self):
        print("异步连接数据库")
        await asyncio.sleep(1)  # 模拟异步连接
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("异步关闭数据库连接")
        await asyncio.sleep(0.5)  # 模拟异步关闭

async def main():
    async with AsyncDatabaseConnection() as db:
        print("使用异步数据库连接")

# asyncio.run(main())

七、注意事项

异常处理

  1. __exit__ 方法会接收异常信息
  2. 如果 __exit__ 返回 True,异常会被抑制,不会传播
  3. 通常应该返回 False 让异常正常传播

资源泄漏

虽然 with 语句能自动管理资源,但如果在 __enter__ 中获取资源失败,__exit__ 可能不会被调用

class RiskyResource:
    def __enter__(self):
        print("获取资源")
        if some_condition:  # 如果这里失败
            raise Exception("获取资源失败")
        return self
    
    def __exit__(self, *args):
        print("释放资源")  # 如果 __enter__ 失败,这行不会执行

性能考虑

with 语句会有轻微的性能开销

在性能敏感的循环中,应考虑在循环外部使用 with

嵌套使用

# 嵌套的 with 语句
with context1() as c1:
    with context2() as c2:
        # 操作 c1 和 c2
        pass

八、实际应用案例

事务管理

class Transaction:
    def __init__(self, db):
        self.db = db
        self.committed = False
    
    def __enter__(self):
        print("开始事务")
        return self
    
    def commit(self):
        self.committed = True
        print("提交事务")
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None or not self.committed:
            print("回滚事务")
        else:
            print("事务完成")

# 使用事务
db = "模拟数据库"
with Transaction(db) as tx:
    # 数据库操作
    if operation_successful:
        tx.commit()

性能分析

import time
from contextlib import contextmanager

@contextmanager
def profile(operation_name):
    start = time.perf_counter()
    try:
        yield
    finally:
        end = time.perf_counter()
        print(f"{operation_name} 耗时: {end - start:.4f}秒")

# 分析代码性能
with profile("数据处理"):
    # 需要分析的代码
    time.sleep(0.1)

总结

with 语句是 Python 中非常重要的资源管理工具,主要有一下特点:

  1. 使代码更简洁、可读
  2. 确保资源正确释放,避免泄漏
  3. 提供统一的异常处理机制
  4. 支持自定义上下文管理器
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐