Python3中上下文管理器介绍

x33g5p2x  于2021-11-11 转载在 Python  
字(4.0k)|赞(0)|评价(0)|浏览(250)

    在任何编程语言中,文件操作或数据库连接等资源的使用都很常见。但这些资源供应有限。因此,主要问题在于确保在使用后释放这些资源。如果不释放它们,则会导致资源泄漏,并可能导致系统变慢或崩溃。如果用户有一个自动设置和拆卸资源的机制,这将是非常有帮助的。在Python3中,可以通过使用上下文管理器(context manager)来实现,这有助于正确处理资源。上下文管理器是一个对象。

    在Python3中,可以通过上下文管理器来管理资源。可通过三种方式创建上下文管理器:基于类的上下文管理器、基于生成器的上下文管理器和基于函数装饰器的上下文管理器。

    **当使用类创建上下文管理器时,用户需要确保该类具有__enter__()和__exit__()**方法enter()返回需要管理的资源;而__exit__()不返回任何内容,而是执行清理操作。

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

    执行文件操作的最常见方式是使用with关键字。Python的with语句支持由上下文管理器定义的运行时上下文的概念。这是使用一对方法(enter, exit)实现的,这些方法允许用户自定义的类在语句体执行之前进入并在语句结束时退出的运行时上下文。

    with语法:

with EXPRESSION [as TARGET]:
    SUITE

    with语句执行过程如下

    (1).评估上下文表达式以获得上下文管理器;

    (2).上下文管理器的__enter__()被加载以备后用;

    (3).上下文管理器的__exit__()被加载以备后用;

    (4).上下文管理器的__enter__()方法被调用;

    (5).如果TARGET包含在with语句中,则将__enter__()的返回值赋值给它。with语句保证如果__enter__()方法没有错误返回,那么__exit__()将始终被调用。因此,如果在赋值给TARGET列表的过程中发生错误,它将被视为SUITE中发生的错误。enter()方法既可以带有返回值也可以不带返回值,若不带返回值则默认返回None。

    (6).SUITE被执行;

    (7).上下文管理器的__exit__()方法被调用。如果异常导致SUITE退出,则其类型、值和回溯(traceback)将作为参数传递给__exit__();否则,传递3个None。exit()方法也是可以带返回值的,这个返回值应该是一个布尔类型True或False,默认为None(即False)。如果为False,异常会被抛出,用户需要进行异常处理。如果为True,则表示忽略该异常。

    如果SUITE因异常退出,并且__exit__()方法返回的值为False,则触发异常。如果返回值为True,则异常被抑制,并继续执行with语句之后的语句。

    如果SUITE因异常以外的任何原因退出,则__exit__()的返回值将被忽略,并在所采取的退出类型的正常位置继续执行。

    contextmanager.enter():输入运行时上下文并返回此对象或与运行时上下文相关的另一个对象。此方法返回的值绑定到使用此上下文管理器的with语句的as子句中指定的目标(如果有)。返回自身的上下文管理器的一个示例是文件对象。文件对象从__enter__()返回自身以允许open()用作with语句中的上下文表达式。

    contextmanager.exit(exc_type, exc_val, exc_tb):退出运行时上下文并返回一个布尔标志,指示是否应抑制发生的任何异常。如果在执行with语句主体时发生异常,则参数包含异常类型、值和回溯信息(traceback information)。否则,所有三个参数都是None。从此方法返回True将导致with语句抑制异常并继续执行紧跟在with语句之后的语句。否则,此方法执行完毕后,异常将继续传播。执行此方法期间发生的异常将替换with语句主体中发生的任何异常。传入的异常不应显式重新引发,相反,此方法应返回False以指示该方法成功完成并且不想抑制引发的异常。这允许上下文管理代码轻松检测__exit__()方法是否实际失败。

    上下文管理器的典型用途包括保存和恢复各种全局状态、锁定和解锁资源、关闭打开的文件等。

    何时使用上下文管理器:当你的代码必须打开和关闭连接作为其流程的一部分时;当你的代码必须使用前置条件和后置条件(preconditions and post-conditions)时;以及当你尝试管理系统上的有限资源时,上下文管理器是一个不错的选择。

    以上内容主要参考:

    1. https://docs.python.org/3/reference/datamodel.html#context-managers

    2. https://www.geeksforgeeks.org/context-manager-in-python/

    以下为测试代码:

import contextlib

var = 3

if var == 1:
    # reference: https://www.geeksforgeeks.org/context-manager-in-python/
    # 基于类的上下文管理器
    class FileManager():
        def __init__(self, filename, mode):
            self.filename = filename
            self.mode = mode
            self.file = None

        def __enter__(self):
            self.file = open(self.filename, self.mode)
            return self.file

        def __exit__(self, exc_type, exc_value, exc_traceback):
            self.file.close()

    # loading a file
    with FileManager('test.txt', 'w') as f:
        f.write('Test')

    print(f.closed) # True
elif var == 2:
    # https://medium.com/swlh/3-ways-to-create-context-managers-in-python-a88e3ba536f3
    # 基于生成器的上下文管理器:代替__enter__和__exit__方法,生成器和@contextlib.contextmanager装饰器将在yield语句之前运行所有内容,就好像它是__enter__方法的一部
    # 分一样,产生的值可能是__enter__方法将返回的结果.之后,将运行with块中的内容,作为最后一步,将运行yield语句之后的代码,就好像它是__exit__方法一样
    @contextlib.contextmanager
    def file_hanlder(file_name, file_mode):
        file = open(file_name, file_mode)
        print("open file")
        yield file # yeild之前的代码会在上下文管理器中作为__enter__方法执行,并将结果通过yield返回,所有在yield之后的代码会作为__exit__方法执行
        file.close()
        print("close file")

    with file_hanlder("test.txt", "w") as f:
        f.write("Test2")
        print("write file")

    print(f.closed) # True
elif var == 3:
    # https://medium.com/swlh/3-ways-to-create-context-managers-in-python-a88e3ba536f3
    # 基于函数装饰器的上下文管理器:函数装饰器方法的问题之一是无法访问__enter__方法的返回值
    class file_handler_mixin(contextlib.ContextDecorator):
        def __init__(self, file_name, file_mode):
            self._file_name = file_name
            self._file_mode = file_mode
            self._file = None

        def __enter__(self):
            print(f"Enter Method: File Name {self._file_name}")
            self._file = open(self._file_name, self._file_mode) # 无法从装饰函数访问文件对象
            return self._file

        def __exit__(self,exc_type,exc_value,exc_traceback):
            print(f"Exit Method: File Mode {self._file_mode}")
            self._file.close()

    @file_handler_mixin("test.txt", "w")
    def write_to_file():
        print("Not access to the value returned by the __enter__ method")

    write_to_file()

print("test finish")

    GitHubhttps://github.com/fengbingchun/Python_Test

相关文章