跳转至

Python 上下文管理器

概要: Python中实现上下文管理器的3种方式

创建时间: 2022.10.20 21:15:13

更新时间: 2022.10.20 22:21:54

需求示例

脱机对数据库进行备份,即备份前必须让数据库停止运行,备份后必须重新启动数据库。
下面通过三种方式实现上下文管理器

  1. 经典方式,不调用库
  2. 使用 contextlib.contextmanager 进行生成器实现
  3. 使用 contextlib.ContextDecorator 装饰器实现

经典上下文管理器

在不使用任何标准库和第三方库情况下的经典实现如下

Python
run = print


def stop_database():
    run("systemctl stop postgresql.service")


def start_database():
    run("systemctl start postgresql.service")


class DBHandler:
    def __enter__(self):
        stop_database()
        return self

    def __exit__(self, ex_type, ex_value, ex_traceback):
        start_database()


def db_backup():
    run("pg_dump database")


if __name__ == "__main__":
    with DBHandler():
        db_backup()

注意事项

  1. 一般而言,最好让 __enter__返回一个值,用于管理上下文
  2. __exit__ 方法接收一些特殊值,用来处理异常,没有异常的情况下这些值默认为 None

基于 contextlib 的生成器实现

通过 contextlib.contextmanager 以及 yield 生成器方法实现

Python
import contextlib


@contextlib.contextmanager
def db_handler():
    try:
        stop_database()
        yield
    finally:
        start_database()


if __name__ == "__main__":
    with db_handler():
        db_backup()

基于 contextlib 的装饰器实现

通过继承 contextlib.ContextDecorator 类,可以很方便地以装饰器的形式实现上下文管理器,且其优点在于,装饰器只需要被定义一次,就可以处处复用。

Python
import contextlib


class db_handler(contextlib.ContextDecorator):
    def __enter__(self):
        stop_database()
        return self

    def __exit__(self, ex_type, ex_value, ex_traceback):
        start_database()


@db_handler()
def db_backup():
    run("pg_dump database")


if __name__ == "__main__":
    db_backup()

通过装饰器进行异常控制

contextlib 库中,我们可以使用 contextlib.suppress 来忽略特定的异常,示例如下

Python
import contextlib


def div(a, b):
    return a / b


if __name__ == "__main__":
    with contextlib.suppress(ZeroDivisionError):
        div(1, 0)
这样,就可以忽略除数为 0 的特殊情况。

参考