Posts Python-上下文管理器
Post
Cancel

Python-上下文管理器

前言

上下文管理器(context manager),其作用能够帮助自动分配并且释放资源,其中最典型的便是 with 语句:

1
2
3
for x in range(10000000):
    with open('test.txt', 'w') as f:
        f.write('hello')

其等效于:

1
2
3
4
5
f = open('test.txt', 'w')
try:
    f.write('hello')
finally:
    f.close()

可以看到,采用 with 的结构会更加简洁。上下文管理器,通常应用在文件的开关,数据库的开关,线程的lock/release等操作,可以确保用过的资源得到迅速释放,能够有效提高程序的安全性。 从原理上讲,一般分为两种上下文管理器,其功能上是一致的:

  • 基于生成器的上下文管理器:方便简洁,适用于中小型程序
  • 基于类的上下文管理器:更加灵活,适用于大型系统开发

基于类的上下文管理器

类的上下文管理器,关键在于一些关键方法的定义:

  • __init__:创建类的初始化方法
  • __enter__:返回需要被管理的资源
  • __exit__:通常会存在一些释放、清理资源的操作,比如关闭文件等

以创建文件的开关创建的上下文管理器 FileManager 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class FileManager:
    def __init__(self, name, mode):
        print('calling __init__ method')
        self.name = name
        self.mode = mode 
        self.file = None

    def __enter__(self):
        print('calling __enter__ method')
        self.file = open(self.name, self.mode)
        return self.file


    def __exit__(self, exc_type, exc_val, exc_tb):
        print('calling __exit__ method')
        if self.file:
            self.file.close()

with FileManager('test.txt', 'w') as f:
    print('ready to write to file')
    f.write('hello world')

## 输出
calling __init__ method
calling __enter__ method
ready to write to file
calling __exit__ method

另外对于 __exit__ 方法来说,可以用于管理异常(exception),可以通过以下参数: exec_type/exc_val/exc_tb ,分别表示: exception_type/exeception_value/traceback 。当执行含有上下文管理器的 with 语句时,如果有异常抛出,异常的信息则会包含在这三个变量,传入 __exit__() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Foo:
    def __init__(self):
        print('__init__ called')        

    def __enter__(self):
        print('__enter__ called')
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        print('__exit__ called')
        if exc_type:
            print(f'exc_type: {exc_type}')
            print(f'exc_value: {exc_value}')
            print(f'exc_traceback: {exc_tb}')
            print('exception handled')
        return True

with Foo() as obj:
    raise Exception('exception raised').with_traceback(None)

# 输出
__init__ called
__enter__ called
__exit__ called
exc_type: <class 'Exception'>
exc_value: exception raised
exc_traceback: <traceback object at 0x1046036c8>
exception handled

但是,值得注意的是:需要在 __exit__ 方法最后需要返回 True ,否则依然会抛出异常。

基于生成器的上下文管理器

可以采用装饰器 contextlib.contextmanager 来定义上下文管理器,用以支持 with 语句:

上述代码中, file_manager() 为生成器,返回的是文件对象f,当 with 语句执行完后, finally block 会执行关闭文件操作。 这里不需要定义 __enter__()__exit__() 方法,但是需要依赖 @contextmanager

1
2
3
4
5
6
7
8
9
10
11
12
13
from contextlib import contextmanager

@contextmanager
def file_manager(name, mode):
    try:
        f = open(name, mode)
        yield f
    finally:
        f.close()

with file_manager('test.txt', 'w') as f:
    f.write('hello world')
This post is licensed under CC BY 4.0 by the author.

Python-垃圾回收

Python-单元测试