31

当脚本因异常退出时,打开的文件(和其他资源)是否会自动关闭?

我想知道在异常处理期间是否需要关闭资源。

编辑:更具体地说,我正在我的脚本中创建一个简单的日志文件。我想知道在异常情况下是否需要明确关闭日志文件。因为我的脚本有一个复杂的、嵌套的、try/except 块,这样做有点复杂,所以如果 python、CLIB 或操作系统要在脚本崩溃/出错时关闭我的文本文件,我不想浪费太多时间确保文件被关闭。

如果 Python 手册中有关于此的部分,请参考它,但我找不到它。

4

4 回答 4

32

一个相当直接的问题。

两个答案。

一种说法是,“是的。”</p>

另一种说法是,“不!”</p>

两者都有重要的支持。

相信谁?让我试着澄清一下。


这两个答案对他们都有一定的道理,这取决于您关闭文件的意思。

首先,从操作系统的角度考虑关闭文件的含义。

当一个进程退出时,操作系统会清除只有该进程打开的所有资源。否则,性能不佳的程序崩溃但没有释放其资源可能会消耗所有系统资源。

如果 Python 是打开该文件的唯一进程,则该文件将被关闭。同样,操作系统将清除进程分配的内存、仍然打开的任何网络端口以及大多数其他内容。有一些特殊的函数shmat可以创建在进程之外持续存在的对象,但在大多数情况下,操作系统会处理所有事情。

现在,从 Python 的角度来看关闭文件怎么样?如果任何用任何编程语言编写的程序退出,大部分资源都会被清理——但 Python 如何处理标准 Python 程序中的清理工作?

Python 的标准 CPython 实现(与 Jython 等其他 Python 实现相反)使用引用计数来完成大部分垃圾收集。一个对象有一个引用计数字段。每次 Python 中的某些内容获得对某个其他对象的引用时,被引用对象中的引用计数字段就会递增。当引用丢失时,例如,因为变量不再在范围内,引用计数会减少。当引用计数为零时,没有 Python 代码可以再访问该对象,因此该对象被释放。当它被释放时,Python 调用__del__()析构函数

__del__()从操作系统的角度来看,Python 的文件方法会刷新缓冲区并关闭文件。由于引用计数,在 CPython 中,如果您在函数中打开文件但不返回文件对象,那么当函数退出时,文件上的引用计数会降至零,并且文件会自动刷新并关闭。当程序结束时,CPython 取消对所有对象的引用,并且所有对象都调用它们的析构函数,即使程序由于未处理的异常而结束。(这在技术上确实失败了,因为你有一个带有 destructors 的对象循环,至少在3.4 之前的 Python 版本中。)

但这只是 CPython 的实现。Python 语言在Python 语言参考中定义,这是所有 Python 实现都必须遵循的,以便称自己为 Python 兼容的。

语言参考在其数据模型部分解释了资源管理:

一些对象包含对“外部”资源的引用,例如打开的文件或窗口。可以理解,这些资源是在对象被垃圾回收的时候释放的,但是由于垃圾回收并不能保证一定会发生,所以这类对象也提供了一种显式的方式来释放外部资源,通常是 close() 方法。强烈建议程序显式关闭此类对象。'try...finally' 语句和 'with' 语句提供了方便的方法来执行此操作。

也就是说,CPython 通常会立即关闭对象,但这可能会在未来的版本中改变,其他 Python 实现甚至根本不需要关闭对象。

因此,为了可移植性,并且因为显式优于隐式,强烈建议调用close()所有可以 close()d 的内容,finally如果在对象创建之间存在代码并且close()可能引发异常,则在块中执行此操作。或者使用with完成同样事情的语法糖。如果你这样做,那么文件上的缓冲区将被刷新,即使引发了异常。

然而,即使有这样的with声明,同样的底层机制也在起作用。如果程序崩溃的方式不给 Python 的 __del__()方法运行机会,你仍然可以在磁盘上得到一个损坏的文件:

#!/usr/bin/env python3.3

import ctypes

# Cast the memory adress 0x0001 to the C function int f()
prototype = ctypes.CFUNCTYPE(int)
f = prototype(1)

with open('foo.txt', 'w'):
    x.write('hi')
    # Segfault
    print(f())

该程序生成一个长度为零的文件。这是一个异常情况,但它表明即使使用with语句资源也不一定总是按照您期望的方式进行清理。Python 告诉操作系统打开一个文件进行写入,它在磁盘上创建它;Python 写入hi C 库的stdio缓冲区;然后它在with 语句结束之前崩溃,并且由于明显的内存损坏,操作系统尝试读取缓冲区的剩余部分并将它们刷新到磁盘是不安全的。因此,即使有with语句,程序也无法正确清理。哎呀。尽管如此,close()并且with几乎总是有效,而且你的程序总是拥有它们总比没有它们好。

所以答案既不是yes也不是no。大多数普通 CPython 程序在技术上不需要该with语句和。close()但是不使用它们会导致看起来错误的不可移植代码。尽管它们非常 有帮助,但它们仍然有可能在病理情况下失败。

于 2013-07-11T03:17:37.457 回答
28

不,他们没有。

with如果您希望即使发生异常也关闭文件,请使用语句。

文档

with语句用于使用上下文管理器定义的方法包装块的执行。这允许封装常见的 try...except...finally使用模式以方便重用。

来自文档

 该with语句允许以确保始终及时正确地清理它们的方式使用文件等对象。

with open("myfile.txt") as f:
    for line in f:
        print line,

执行语句后,文件f始终关闭,即使在处理行时遇到问题。提供预定义清理操作的其他对象将在其文档中指出这一点。

于 2013-07-10T17:34:36.223 回答
3

是的,他们这样做。

这是一个 CLIB(至少在 cpython 中)和操作系统的东西。当脚本退出时,CLIB 将刷新并关闭所有文件对象。即使它没有(例如,python 本身崩溃),操作系统也会像任何其他进程一样关闭其资源。它是异常还是正常退出,或者即使它的python或任何其他程序都没有关系。

这是一个在文件内容被刷新到磁盘之前写入文件并引发异常的脚本。工作正常:

~/tmp/so$ cat xyz.txt
cat: xyz.txt: No such file or directory
~/tmp/so$ cat exits.py
f = open("xyz.txt", "w")
f.write("hello")
print("file is", open("xyz.txt").read())
assert False

~/tmp/so$ python exits.py
('file is', '')
Traceback (most recent call last):
  File "exits.py", line 4, in <module>
    assert False
AssertionError
~/tmp/so$ cat xyz.txt
hello
于 2013-07-10T18:20:15.253 回答
0

我,以及这个线程中的其他人,都留下了一个问题,“那么最终是真的吗?”

现在,假设文件在程序过早终止时保持打开状态——除了文件处理引起的异常之外,还有很多这样的情况——避免这种情况的唯一安全方法是读取整个(或部分)文件放入缓冲区并关闭它。然后根据需要处理缓冲区中的内容。这是特别的。必须对文件进行的全局搜索、更改等情况。更改完成后,可以一次将整个缓冲区写入同一个文件或其他文件,避免让新创建的文件保持打开状态的风险——通过大量读取和写入——这是最坏的情况!

于 2018-01-14T12:55:15.200 回答