跳转至

上下文管理器

在 Python中,上下文管理器(Context Manager)是一种用于管理资源的对象,这些资源在进入和退出时分别做一些预处理和清理的工作。

with 语句用于管理和调用上下文管理器对象,它会设置一个临时的上下文,交给上下文管理对象控制,并且负责清理上下文。

可以嵌套使用

with A() as a, B() as b:
    pass

# 等价于
with A() as a:
    with B() as b:
        pass

内置的上下文管理器

Python 提供了许多内置的上下文管理器,用于管理各种资源,如文件操作、线程同步、异常处理、标准输出重定向、临时文件、数据库连接、压缩文件等。

通过使用这些内置的上下文管理器,可以确保资源在使用过程中得到正确的管理和释放,写出更加健壮和优雅的代码。

文件操作

with open("filename.xx") as fp:
    """
    1. open() 返回 TextIOWrapper 类的实例,with 将其交给上下文管理器
    2. 调用 TextIOWrapper 的 __enter__ 方法,返回 self
    3. as 语句将 fp 绑定到返回值 self,as 子句其实是可选的,但对于 open() 函数是必须的,以便获取文件的引用
    """
    src = fp.read(60)
    """
    4. 调用 TextIOWrapper 的 __exit__ 方法,扮演 finally 子句的角色
    """

如果不使用 with 打开文件,则需要使用 try...finally 来处理报错

file = open('example.txt', 'r')
try:
    content = file.read()
    print(content)
finally:
    file.close()

处理压缩文件

import tarfile

with tarfile.open('example.tar.gz', 'r:gz') as tar_file:
    tar_file.extractall('extracted')

数据库连接

import sqlite3

with sqlite3.connect('example.db') as conn:
    cursor = conn.cursor()
    cursor.execute('CREATE TABLE IF NOT EXISTS example (id INTEGER PRIMARY KEY, value TEXT)')
    cursor.execute('INSERT INTO example (value) VALUES (?)', ('Hello, world!',))
    conn.commit()

线程锁定

import threading

lock = threading.Lock()

with lock:
    # 执行线程安全的代码
    pass

自定义上下文管理器

自定义上下文管理器,即自定义上下文管理协议中的两个方法:__enter____exit__

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

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        __exit__ 有三个参数:exc_type, exc_val, exc_tb
            如果调用代码块未发生异常,则三个参数全为 None
            否则分别对应异常的 type、value 和 traceback
        __exit__ 的返回值可以为 True 或 False
            如果为 True,那么表示异常被忽视,相当于进行了 try-except 操作
            如果为 False,则该异常会被抛出
        """
        print("Exiting the context")
        if exc_type is not None:
            print(f"An exception occurred: {exc_val}")
        return True  # Suppress the exception

with MyContextManager():
    print("Inside the context")
    # raise ValueError("An error occurred")  # Uncomment to test exception handling
import sqlite3

class DatabaseConnection:
    def __init__(self, db_name):
        self.db_name = db_name

    # 连接数据库
    def __enter__(self):
        self.conn = sqlite3.connect(self.db_name)
        return self.conn

    # 关闭数据库
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.close()

with DatabaseConnection('example.db') as conn:
    cursor = conn.cursor()
    cursor.execute('SELECT * FROM example_table')
    rows = cursor.fetchall()
    print(rows)

装饰器 @contextmanager 实现

装饰器 @contextmanager 可以把生成器函数变成上下文管理器,此时的 yield 关键字与迭代没有任何关系

from contextlib import contextmanager

@contextmanager
def my_context_manager():
    print("Entering the context")
    yield
    print("Exiting the context")

# 也需要使用 with 来调用
with my_context_manager():
    print("Inside the context")

"""
Entering the context
Inside the context
Exiting the context
"""