使用 contextlib 自动关闭资源

在Python中, 总有一些需要"善后"的事情要做, 比如说打开文件后自动关闭文件描述符, 比如说想要显示的释放某种资源, 比如...

上面这种需求很常见的一个场景就是读取文件, 很方便的一种做法是使用with语句来控制

with open('a.txt' 'r') as f:
    f.readline()

这样写的好处是, 在with里边的代码块执行完毕后, 会自动的关闭关闭文件. 而且这种写法可读性高, 犯错的几率也会变小.
那么, 包含with的代码块在执行的时候都做了什么呢? 它的执行过程又是怎样的呢?

  1. 首先会计算表达式的值, 返回一个上下文管理器对象
  2. 调用上下文管理器对象的__enter__()方法
  3. 如果with语句中设置了as目标对象, 将__enter__()返回的返回值赋给目标对象
  4. 执行with中的代码块
  5. 调用上下文对象的__exit__()方法, 如果with中代码块中有异常, 那么会将exception_type, exception_value, traceback传给__exit__(), 如果其返回False那么异常会被重新抛出, 否则异常被挂起, 程序继续执行

OK, 那么, 什么是上下文管理器呢?

上下文管理器是一个对象, 它定义了程序在执行过程中的上下文, 处理程序的运行和退出, 实现了上下文管理协议, 即在对象中定义__enter__()__exit__()方法
简单讲, 就是实现了__enter__()__exit__()两个方法的对象, 我们都可以称之为上下文管理器

例如, 自己实现一个HTML标签上下文管理器

class HTMLContextManager(object):
    def __init__(self, tag):
        self._tag = tag

    def __enter__(self):
        print('<{}>'.format(self._tag))

    def __exit__(self, exception_type, exception_value, traceback):
        print('</{}>'.format(self._tag))
        return False


with HTMLContextManager('h1'):
    print('I am body')

输出结果如下

<h1>
I am body
</h1>

可以说是非常的方便了, 在实际的应用当中, 可以把上下文管理器用作锁的控制, 文件的打开关闭, 异常处理等等, 在__enter__()方法中实现资源分配, 预处理工作, 在__exit__()方法中实现资源释放, 善后工作

但是, 如果想把一个函数对象包装成一个上下文管理器怎么办呢? Python的标准库提供了更加易用的contextlib上下文管理工具模块, 它可以通过生成器实现, 不需要显示创建类以及__enter__()__exit__()两个方法

示例:

@contextmanager
def html_context_manager(tag):
    print('<{}>'.format(tag))
    yield
    print('</{}>'.format(tag))


with html_context_manager('h2'):
    print('I am body')

输出结果如下

<h2>
I am body
</h2>

比写一个类更加简便了! 但是这种写法, 如果with代码块中的代码抛出了异常, 默认是向上抛出异常, 程序挂起的, 也就是类中__exit__()方法返回False
还有一种比较好用的用法是把一个类包装成一个上下文管理器, 即

class MyClass(object):
    def __init__(self):
        pass


@contextmanager
def my_context_manager():
    print('before call')
    yield MyClass()
    print('after call')

未经允许禁止转载 https://spxcds.com/2018/12/17/python_contextlib

猜你喜欢

转载自www.cnblogs.com/spxcds/p/10204421.html