URLhttps://learnscript.net/zh/python/statements/with/
    复制链接转到说明  示例

    Python with 语句,with 语句上下文管理器介绍

    我被代码海扁署名-非商业-禁演绎
    阅读 8:32·字数 2562·发布 

    with 语句

    如果你希望能够及时释放重要的资源,以防止其因为某些情况(比如异常)而始终处于被占用的状态,或者希望临时保存当前状态以便在稍微恢复,那么 Python 提供的with语句可用于完成上述目标,该语句仅接受with语句上下文管理器作为处理目标,其基本书写格式如下。

    with <expression>[ as <target>]:
        <block>

    expression 部分

    expression是一个运算结果为上下文管理器的表达式,比如,一个调用内置函数open获得文件对象的表达式(Python 文件对象是有效的上下文管理器)。

    target 部分

    target是变量的名称,该变量将存储上下文管理器(即expression部分的运算结果)的__enter__方法的返回值。变量名称需要符合 Python 的标识符规范,不能使用 Python 关键字或保留关键字。

    block 部分

    blockwith语句的主体代码,需要使用某种空白字符进行缩进,以表示其归属于with语句。

    with语句开始执行时,expression部分所返回的上下文管理器的__enter__方法将被调用,你可以在该方法中保存一些重要的数据或状态。

    with语句结束执行时,上下文管理器的__exit__方法将被调用,这里所说的语句结束包括正常结束和非正常结束(比如,block部分的代码出现了异常)。由于,上下文管理器的__exit__方法总是被调用,因此__exit__方法可用于释放一些重要的资源,或恢复之前保存的数据或状态。

    上下文管理器的 __enter__ 方法可以返回自身

    with语句对上下文管理器的__enter__方法的返回值的类型没有要求,因此,__enter__方法可以返回上下文管理器自身,以方便代码的编写和阅读。

    下面,我们使用with语句来打开文件with.txt,由于 Python 文件对象的__enter__方法返回其自身,因此变量file指向文件对象,可以使用他将内容写入文件。

    with语句结束时,文件对象的__exit__方法会被调用,这将使文件对象被关闭,再次调用其write方法将引发异常。

    with.py
    # 文件对象的 __enter__ 方法返回自身
    with open('with.txt', 'w', encoding='utf8') as file:
    	file.write('with 语句')
    
    # ERROR 文件对象已经被关闭 file.write('又一条 with 语句')
    ValueError: I/O operation on closed file.

    with 语句的上下文管理器

    with语句的上下文管理器应该拥有__enter____exit__方法,如前所述,他们分别在with语句开始和结束时被调用,__enter__方法的返回值将作为with…as语句所定义的变量的值。

    __enter__(self)

    如果with语句的主体代码引发了异常并且没有处理,那么异常的信息将被传递给上下文管理器的__exit__方法的三个参数,如果此时__exit__方法返回True或被视为True的值,那么异常将被抑制,后续代码可以在不处理异常的情况下继续运行。

    如果with语句的主体代码没有引发异常或者异常得到了处理,那么with语句上下文管理器的__exit__方法的三个参数将为None

    __exit__(self, exc_type, exc_value, exc_tb)

    exc_type 参数

    exc_type参数是一个type对象,表示with语句的主体代码所引发的异常的类型。

    exc_value 参数

    exc_value参数为with语句的主体代码所引发的异常。

    exc_tb 参数

    exc_tb参数表示with语句的主体代码所引发的异常的回溯类型。

    不需要在上下文管理器的 __exit__ 方法中再次抛出异常

    with语句的主体代码引发了异常且异常没有被处理时,不需要在上下文管理器的__exit__方法中再次抛出异常,只要确保__exit__方法没有返回True或被视为True的值,异常便可继续传播,即with语句以外的代码应负责处理异常。

    下面,我们定义了自己的上下文管理器CMCM__enter__方法用于保存模块变量students中的列表,CM__exit__方法用于恢复模块变量students中的列表。

    with_cm.py
    # 模块变量 students
    students = ['小红', '小黑']
    
    # 自定义 with 语句上下文管理器 CM class CM: def __enter__(self): # 将模块变量 students 中的列表保存到 CM 中 global students self.students = students
    # 将新的列表赋值给模块变量 students students = ['小兰', '小白'] return students
    def __exit__(self, exc_type, exc_value, exc_tb): # 恢复模块变量 students 中原有的列表 global students students = self.students
    # CM 会临时改变模块变量 students with CM() as s: print(s, students)
    print(students)
    ['小兰', '小白'] ['小兰', '小白']
    ['小红', '小黑']

    在下面的示例中,with语句引发了异常ZeroDivisionError,该异常的相关信息被传递给上下文管理器对象CM__exit__方法,由于__exit__方法返回了True,因此异常ZeroDivisionError将被抑制。

    with_cm_exc.py
    # 自定义 with 语句上下文管理器 CM
    class CM:
    	def __enter__(self):
    		pass
    	def __exit__(self, exc_type, exc_value, exc_tb):
    		print(exc_type, exc_value, exc_tb)
    		# 返回 True 后,异常将被抑制
    		return True
    
    with CM(): # 异常 ZeroDivisionError 被引发并且没有得到处理 num = 1 / 0
    <class 'ZeroDivisionError'> division by zero <traceback object at >

    在 with 语句中包含多个上下文管理器

    一般情况下,with语句只处理一个上下文管理器,但如果有需要,你可以在with语句中包含多个上下文管理器,并使用逗号,分隔表达式,比如expression1 as target1,expression2 as target2。当然,你还可以使用括号()括住所有上下文管理器对应的表达式,这样每一个表达式可单独占用一行。需要指出,这里的括号()并不会创建 Python 元组。

    如果with语句包含了多个上下文管理器,那么将按照顺序依次调用他们的__enter__方法,并按照相反的顺序调用他们的__exit__方法。

    在下面的示例中,with语句包含了两个上下文管理器,我们姑且称他们为AB。对于__enter__方法,将按照AB的顺序被调用,对于__exit__方法,将按照BA的顺序被调用。

    with_cm_multi.py
    # 自定义 with 语句上下文管理器 CM
    class CM:
    	# CM 的构造器
    	def __init__(self, name):
    		self.name = name
    
    # __enter__ 和 __exit__ 方法将显示 CM 的名称 def __enter__(self): print(f'__enter__ {self.name}') def __exit__(self, exc_type, exc_value, exc_tb): print(f'__exit__ {self.name} {exc_type}')
    # 包含了两个上下文管理器 with ( CM('A'), CM('B') ): pass
    __enter__ A
    __enter__ B
    __exit__ B None
    __exit__ A None

    异常会按照从后向前的顺序在多个上下文管理器中传播

    除非异常被处理或被__exit__方法抑制,否则他将按照从后向前的顺序在with语句的多个上下文管理器中传播。

    我们为上面的示例增加一些代码,在with语句中引发异常ZeroDivisionError,异常将按照BA的顺序传播。

    with_cm_multi.py
    # …
    # 异常将按照 B,A 的顺序传播
    with CM('A'), CM('B'):
    	num = 1 / 0
    __exit__ B <class 'ZeroDivisionError'>
    __exit__ A <class 'ZeroDivisionError'>

    ZeroDivisionError: division by zero

    源码

    src/zh/statements/with·codebeatme/python·GitHub