返回介绍

建议22:使用 with 自动关闭资源

发布于 2024-01-30 22:19:09 字数 3287 浏览 0 评论 0 收藏 0

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

>>> f = open('test.txt', 'w')
>>> f.write("test")

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

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

with 
表达式  [ as 
目标]
:
代码块

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

with  expr1 as e1:
  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__()方法。如果_exit__()返回值为false,则异常会被重新抛出;如果其返回值为true,异常被挂起,程序继续执行。

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

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

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

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

>>> f.__enter__
<built-in method __enter__ of file object at 0x029F0700>
>>> f.__exit__
<built-in method __exit__ of file object at 0x029F0700>

用户也可以定义自己的上下文管理器来控制程序的运行,只需要实现上下文协议便能够和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..."
...
entering...
Testing...
leaving...
no exceptions!
>>>

因为上下文管理器主要作用于资源共享,因此在实际应用中__enter()__和__exit()__方法基本用于资源分配以及释放相关的工作,如打开/关闭文件、异常处理、断开流的连接、锁分配等。为了更好地辅助上下文管理,Python还提供了contextlib模块,该模块是通过Generator实现的,contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制,可以直接作用于函数/对象而不用去关心__enter()__和__exit()__方法的具体实现。关于contextlib更多内容读者可以参考网页http://docs.python.org/2/library/contextlib.html。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文