Python的上下文管理器

什么是上下文管理器

上下文管理器是一个对象,它定义了在执行 with 语句时要建立的运行时上下文。 上下文管理器处理进入和退出所需运行时上下文以执行代码块。 通常使用 with 语句(在 with 语句中描述),但是也可以通过直接调用它们的方法来使用。

首先我们看下面操作文件的代码,理清几个概念,不要弄混了

with open("test.txt") as f:
    print(f.readlines())
  • with open("test.txt") as f:上下文表达式
  • open("test.txt"):上下文管理器
  • f:至于f,f不是上下文管理器,f应该是资源对象

上下文管理协议

  • 在一个类中,如果实现了__enter____exit__这两个魔法方法,这个类的实例就是一个上下文管理器。
  • 如果使用了上下文管理器,尽管with没有调用魔法方法,但是with在代码块执行前还是会先执行__enter__,在代码执行结束或出错的时候执行__enter__
  • __enter__: with语句中的代码块执行前执行__enter__, 返回的值将赋值给with句中as后的变量.
  • __exit__: with语句中的代码块执行结束或出错, 会执行__exit__
class Resource():
    def __enter__(self):
        print('===connect to resource===')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('===close resource connection===')

    def operate(self):
        print('===in operation===')


with Resource() as res:
    res.operate()

"""
输出

===connect to resource===
===in operation===
===close resource connection===
"""
  • 在编写代码时,我们一般将资源的连接或者获取放在__enter__中,而将资源的关闭写在__exit__ 中。

为什么要使用上下文管理器

  1. 使用上下文管理器会让代码看起来更简洁优雅,这也是Python一直追求的。我们可以用上下文管理器操作(创建/获取/释放)资源,如文件操作、数据库连接
  2. 也可以用上下文管理器处理异常。我们一般用try...except...来处理异常但是这样做一个不好的地方是,在代码的主逻辑里,会有大量的异常处理代理,这会很大的影响我们的可读性。如果用上下文管理器,就可以使用with将异常的处理隐藏起来。也就是说,with大大简化了try...except..语句的异常处理

举个栗子,下面的代码我们将操作1/0 这个错误。看看是否不报错。

class Resource():
    def __enter__(self):
        print('===connect to resource===')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('===close resource connection===')
        print(exc_type, exc_val, exc_tb)
        return True

    def operate(self):
        1/0  # 分母不能为0,这里应该报错


with Resource() as res:
    res.operate()

"""
输出

===connect to resource===
===close resource connection===
 division by zero 
"""

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

在 写__exit__ 函数时,需要注意的事,它必须要有这三个参数:

  • exc_type:异常类型
  • exc_val:异常值
  • exc_tb:异常的错误栈信息

当主逻辑代码没有报异常时,这三个参数将都为None。

理解并使用装饰器 contextlib

上面说了,如果要定义上下文管理器,就需要在类中定义__enter____exit__。在Python中也提供了一个@contextlib装饰器,可以省略两个魔法方法。该装饰器位于contextlib模块下

from contextlib import contextmanager
  • 我们借助contextmanager装饰器,可以不使用两个魔法方法。但是这里注意我们只是不需要定义__enter____exit__这两个方法,但是他们里面所执行的语句我们还是需要实现的。在进入上下文管理器的时候打印__enter__里面的方法,在退出的时候打印__exit__里面的方法。
from contextlib import contextmanager

@contextmanager
def open_func(file_name):
    # __enter__ 方法
    print('open file:', file_name, 'in __enter__')
    file_handler = open(file_name, 'r')

    # 【重点】:yield 返回的内容复制给as之后的变量
    yield file_handler

    # __exit__方法
    print('close file:', file_name, 'in __exit__')
    file_handler.close()
    return

with open_func('E:/hello.txt') as f:
    for line in f:
        print(line)
        
"""
输出

open file: E:/hello.txt in __enter__
1

2

3

close file: E:/hello.txt in __exit__
"""

上面代码的执行过程:

  • with语句中的代码块执行函数中yield语句之前的代码,相当于执行__enter__ 方法。
  • yield返回的内容复制给as之后的变量,也就是f。 在被装饰函数里,必须是一个生成器(带有yield)
  • with语句中的代码块执行函数中yield语句之后的代码,相当于执行__exit__ 方法。

上下文管理器的三个好处

  1. 提高代码的复用率
  2. 提高代码的优雅度
  3. 提高代码的可读性

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