上下文管理协议(context management protocol) 上下文管理器(context manager)

上下文管理协议(context management protocol):
包含enter()和exit()方法。

上下文管理器(context manager):
在with语句中使用,实现了enter()和exit()方法的对象。如果一个类实现了enter()和exit()方法,那么这个类就是一个上下文管理器对象。

例如,类A就是一个上下文管理器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class A:
    def __init__(self,name):
        self.name = name

    def __enter__(self):
        print("进入")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("退出")

    def read(self):
        print("读取数据")

with语句

with语句用于包装带有使用上下文管理器定义的方法的代码块的执行。基本语法结构如下:

1
2
with 上下文表达式 [as 目标]:
    语句块

with 语句的执行过程如下:

  1. 对上下文表达式求值以获得一个上下文管理器。
  2. 载入上下文管理器的exit()以便后续使用。
  3. 调用上下文管理器的enter()方法。
  4. 如果with语句中包含一个目标,来自enter() 的返回值将被赋值给它。
    注意,with 语句会保证如果enter()方法返回时未发生错误,则exit()将总是被调用。 因此,如果在对目标列表赋值期间发生错误,则会将其视为在语句体内部发生的错误。 参见下面的第 6 步。
  5. 执行语句块。
  6. 调用上下文管理器的exit()方法。 如果语句体的退出是由异常导致的,则其类型、值和回溯信息将被作为参数传递给exit()。 否则的话,将提供三个 None 参数。 如果语句体的退出是由异常导致的,并且来自exit()方法的返回值为假,则该异常会被重新引发。 如果返回值为真,则该异常会被抑制,并会继续执行 with 语句之后的语句。 如果语句体由于异常以外的任何原因退出,则来自exit() 的返回值会被忽略,并会在该类退出正常的发生位置继续执行。

如果有多个,则会视作存在多个 with 语句嵌套来处理多个上下文管理器:

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

等价于

1
2
3
with A() as a:
    with B() as b:
        pass

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class A:
    def __init__(self,name):
        self.name = name

    def __enter__(self):
        print("进入")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("退出")

    def read(self):
        print("读取数据")

a = A("test")
with a as f:
    f.read()

执行以上程序会输出如下结果:

1
2
3
进入
读取数据
退出

为什么要使用上下文管理器?
可以以一种更加优雅的方式,操作(创建/获取/释放)资源,如文件操作、数据库连接。还有另外一个原因,可以以一种更加优雅的方式,处理异常。

例如,以下程序不会抛出异常。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class A:
    def __init__(self,name):
        self.name = name

    def __enter__(self):
        print("进入")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("退出")
        return True

    def read(self):
        print("读取数据")
        1/0

a = A("test")
with a as f:
    f.read()

执行以上程序会输出如下结果:

1
2
3
进入
读取数据
退出

这就是上下文管理协议的一个强大之处,异常可以在exit 进行捕获并由你自己决定如何处理,是抛出呢还是在这里就解决了。在exit 里返回 True(没有return 就默认为 return False),就相当于告诉 Python解释器,这个异常我们已经捕获了,不需要再往外抛了。

exit方法必须要有这三个参数:
exc_type:异常类型
exc_val:异常值
exc_tb:异常的错误栈信息
当主逻辑代码没有报异常时,这三个参数将都为None。

理解并使用contextlib模块

Python的contextlib模块给我们提供了更方便的方式来实现一个自定义的上下文管理器。contextlib模块包含一个装饰器contextmanager和一些辅助函数,装饰器contextmanager只需要写一个生成器函数就可以代替自定义的上下文管理器,典型用法如下:

1
2
3
4
5
6
7
@contextmanager
def some_generator(<arguments>):
    <setup>
    try:
        yield <value>
    finally:
        <cleanup>

然后便可以用with语句调用contextmanage生成的上下文管理器了,with语句用法如下:

1
2
with some_generator(<arguments>) as <variable>:
    <body>

例子1:文件打开后自动管理的实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import contextlib
@contextmanager
def myopen(filename, mode="r"):
    f = open(filename,mode)
    try:
        yield f
    finally:
        f.close()

with myopen("test.txt") as f:
    for line in f:
        print(line)

例子2:数据库事务的处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import contextlib
@contextmanager
def transaction(db):
    db.begin()
    try
        yield
    except:
        db.rollback()
        raise
    else:
        db.commit()

with transaction(mydb):
    mydb.cursor.execute(sql)
    mydb.cursor.execute(sql)
    mydb.cursor.execute(sql)
    mydb.cursor.execute(sql)

转载请注明本网址。