Python 笔记(12)— 上下文管理器和 with 语句

1. 上下文管理器概念

什么是 Python 的上下文管理器(Context Managers)呢?

含有 __enter____exit__ 方法的对象就是。上下文管理器存在的目的是管理 with 语句,就像是迭代器的存在是为了管理 for 语句一样。

with 上下文管理器:
	语句体

2. with 语句

with 遇到上下文管理器,就会在执行语句体之前,先执行上下文管理器的 __enter__ 方法,然后再执行语句体,执行完语句体后,最后执行 __exit__ 方法。举个例子:

class Foo(object):

    def __init__(self):
        print('把大象放冰箱总共分几步?')

    def __enter__(self):
        print('把冰箱门打开')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('把冰箱门关上')

obj = Foo()

with obj:
    print('把大象放进去')

输出:

把大象放冰箱总共分几步?
把冰箱门打开
把大象放进去
把冰箱门关上

with 有一个很重要的作用是简化 try/finally 模式,无论何种异常发生,finally 字句都会执行。

通常 finally子句中的代码用于释放重要的资源,或者还原临时变更的状态。比如,不管大象能不能装进冰箱,最终 finally你都得把冰箱门关上,用代码解释就是:

class Foo(object):

    def __init__(self):
        print('把大象放冰箱总共分几步?')

    def __enter__(self):
        print('把冰箱门打开')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('把冰箱门关上')

obj = Foo()

with obj:
    raise ImportError # 这里发生严重的错误,这段程序崩溃了
    print('把大象放进去')

输出结果:

把大象放冰箱总共分几步?
把冰箱门打开
把冰箱门关上
Traceback (most recent call last):
  File "char_1.py", line 15, in <module>
    raise ImportError
ImportError

有了 with 保驾护航,装大象的程序崩溃了,也能把冰箱门关上。

3. 上下文管理器写法

Python 提供了更简便的上下文管理器写法,使用 @contextmanager

在使用 @contextmanager 装饰的生成器中,yield 语句的作用是把所在函数分成两部分:

  • yield 语句前面的代码在 with 块开始时执行;
  • yield 语句后面的代码在 with 块结束时执行;

那么上面的例子就可以简写成为:

from contextlib import contextmanager

@contextmanager
def fridge_operation():
    print('把大象放冰箱总共分几步?')
    print('把冰箱门打开')
    yield
    print('把冰箱门关上')


def put_elephant_in_fridge():
    print('把大象放进去')

with fridge_operation() :
    put_elephant_in_fridge()

输出:

把大象放冰箱总共分几步?
把冰箱门打开
把大象放进去
把冰箱门关上

但需要特别注意的是,如果使用 @contextmanager 来实现上下文管理器,要把 yield 语句放在 try/finally语句中(或者放在 with 语句中)。

个人觉得 Python 在这一点上把 @contextmanager 变的十分具有迷惑性,它可以把生成器函数变成上下文管理器,但是它不能联手与 with 默认完成 finally 功能,用代码说明:

from contextlib import contextmanager

@contextmanager
def fridge_operation():
    print('把大象放冰箱总共分几步?')
    print('把冰箱门打开')
    yield
    print('把冰箱门关上')

def put_elephant_in_fridge():
    print('把大象放进去')

with fridge_operation() :
    raise ImportError # 这里发生严重的错误,这段程序崩溃了
    put_elephant_in_fridge()

输出:

把大象放冰箱总共分几步?
把冰箱门打开
Traceback (most recent call last):
  File "char_1.py", line 15, in <module>
    raise ImportError
ImportError

发现了么?冰箱门没关上。冰箱门没关上,我要你上下文管理器有什么用?所以我们需要把 yield 语句放在 try/finally 语句中,如下:

from contextlib import contextmanager

@contextmanager
def fridge_operation():
    print('把大象放冰箱总共分几步?')
    print('把冰箱门打开')
    try:
        yield
    finally:
        print('把冰箱门关上')

with fridge_operation():
    raise ImportError
    print('把大象放进去')

输出:

把大象放冰箱总共分几步?
把冰箱门打开
把冰箱门关上
Traceback (most recent call last):
  File "char_1.py", line 13, in <module>
    raise ImportError
ImportError

至此,@contextmanager 巧妙的将三个不同的 Python 特性结合到了一起:函数装饰器,生成器和 with 语句。

参考:
https://book.pythontips.com/en/latest/context_managers.html

你可能感兴趣的:(Python,python,with,上下文管理器)