美文网首页
Python的上下文管理器

Python的上下文管理器

作者: 时间煮菜 | 来源:发表于2020-10-22 18:26 被阅读0次

    什么是上下文管理器

    上下文管理器是一个对象,它定义了在执行 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===
    <class 'ZeroDivisionError'> division by zero <traceback object at 0x0000024A3C452E08>
    """
    

    这就是上下文管理协议的一个强大之处,异常可以在__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的上下文管理器

          本文链接:https://www.haomeiwen.com/subject/pjewrktx.html