>>> from env_helper import info; info()
页面更新时间: 2023-08-12 10:17:27
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-11-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

3.4. 使用with自动关闭资源

来做个简单的试验,观察一下发生的现象4在Python解释器中输人下面两行代码,会有什么情况发生呢?

>>> f = open ("test1.txt" ,'w')
>>> f.write ("test")
4
>>> !cat test1.txt

答案是:当前目录下生成了一个文件 test1.txt ,并且在里面写人了字符串 test 。 对吗?事实真相是:的确生成了一个文件,但其内容为空,并没有写人任何字符串。 这个一个简单得不能再简单的问题,相信不用多说你已经知道症结所在了。

对文件操作完成后应该立即关闭它们,这是一个常识,我们都知道需要这么做, 在很多编程语言中都会强调这个问题,因为打开的文件不仅会占用系统资源, 而且可能影响其他 程序或者进程的操作,甚至会导致用户期望与实际操作结果不一致。 但实际应用中真相往往 是:即使我们心中记得这个原则,但仍然可能会忘记关闭它。 为什么?因为编程人员会把更 多的精力和注意力放在对具体文件内容的操作和处理上; 或者设计的正常流程是处理完毕关闭文件,但结果程序执行过程中发生了异常导致关闭文件的代码没有被执行到。 也许你会 说,还有 try finally 块。对!这是一种比较古老的方法,但Python提供了一种更为简单的解 决方案: with 语句。

with 语句的语法为:

with 表达式 [as 目标]:

代码块

with 语句支持嵌套,支持多个 with 子句,它们两者可以相互转换。 with exprl as el,expr2 as e2 与下面的嵌套形式等价:

with exprl as el:
    with expr2 as e2:

with 语句的使用非常简单,本节开头的例子改用 with 语句能够保证当写操作执行完毕后自动关闭文件。

>>> with open ('test.txt','w') as f:
>>>     f.write ("test")

with 语句可以在代码块执行完毕后还原进入该代码块时的现场,包含有 with 语句的代 码块的执行过程如下:

  1. 计算表达式的值,返回一个上下文管理器对象。

  2. 加载上下文管理器对象的 __exit__() 方法以备后用。

  3. 调用上下文管理器对象的 __enter__() 方法。

  4. 如果 with 语句中设置了目标对象,则将 __enter__() 方法的返冋值赋值给目标对象。

  5. 执行 with 中的代码块。

  6. 如果步骤 5 中代码正常结束,调用上下文管理器对象的 __exit__() 方法,其返回值直 接忽略。

  7. 如果步骤5中代码执行过程中发生异常,调用上下文管理器对象的 __exit__() 方法, 并将异常类型、值及traceback信息作为参数传递给 __exit__() 方法。 如果返回值为 False , 则异常会被重新抛出;如果其返回值为 True ,异常被挂起,程序继续执行。

在文件处理时使用 with 的好处在于无论程序以何种方式跳出 with 块,总能保证文件被 正确关闭。 实际上它不仅仅针对文件处理,针对其他情景同样可以实现运行时环境的清理 与还原,如多线程编程中的锁对象的管理。 with 的神奇实际得益于一个称为上下文管理器 (context manager)的东西:它用来创建一个运行时的环境。 上下文管理器是这样一个对象: 它定义程序运行时需要建立的上下文,处理程序的进入和退出,实现了上下文管理协议, 即在对象中定义 __enter__()__exit__() 方法。其中:

  • __enter__() 进入运行时的上下文,返回运行时上下文相关的对象,with语句中会将 这个返回值绑定到目标对象。如上面的例子中会将文件对象本身返回并绑定到目标 f

  • __exit__(cxception_typc,exception_va]ue,traceback) : 退出运行时的上下文,定义在块 执行 <或终止)之后上下文管理器应该做什么。它可以处理异常、清理现场或者处理 with 块中语句执行完成之后需要处理的动作。

实际上任何实现了上下文协议的对象都可以称为一个上下文管理器, 文件也是实现了这个协议的上下文管理器,它们都能够与 with语句兼容。文件对象的 __enter____exit__ 属性如下:

>>> f.__enter__
<function TextIOWrapper.__enter__>
>>> f.__exit__
<function TextIOWrapper.__exit__>

用户也可以定义自己的上下文管理器来控制程序的运行,只需要实现上下文协议便能够和 with 语句一起使用。

>>> class MyContextManager (object):
>>>     def __enter__(self):
>>>         # 实现_enter_方法 ...
>>>         print ("entering.")
>>>     def __exit__(self ,exception_type, exception_value, traceback):
>>>         print  ("leaving...")
>>>         if exception_type is None:
>>>             print  ("no exceptions!")
>>>             return False
>>>         elif exception_type is ValueError:
>>>             print  ("value error!!!")
>>>             return True
>>>         else:
>>>             print  ("other error")
>>>             return True
>>> with MyContextManager () :
>>>     print  ("Testing...")
>>>     raise(ValueError)
entering.
Testing...
leaving...
value error!!!
>>> with MyContextManager ():
>>>     print  ("Testing. ")
>>>     raise(ValueError)
entering.
Testing.
leaving...
value error!3 i

因为上下文管理器主要作用于资源共享,因此在实际应用中 __enter__()__exit__() 方法基本用于资源分配以及释放相关的工作, 如打开/关闭文件、异常处理、断开流的连接、 锁分配等。

为了更好地辅助上下文管理,Python还提供了 contextlib 模块,该模块是通过 Generator实现的, coatextlib 中的 contextmanager 作为装饰器来提供一种针对函数级別的上 下文管理机制, 可以直接作用于函数/对象而不用去关心 __enter__()__exit__() 方法的具 体实现。