os.fdopen() 的语义是什么?

4 投票
1 回答
1271 浏览
提问于 2025-04-18 02:03

我以前以为 os.fdopen() 要么会消耗掉文件描述符并返回一个文件对象,要么会抛出一个异常。

比如说:

fd = os.open("/etc/passwd", os.O_RDONLY)
try: os.fdopen(fd, "w")
except: os.close(fd)  # prevent memory leak

但是这种理解似乎并不总是正确。

这里有一个在OSX上的例子:

In [1]: import os

In [2]: os.open("/", os.O_RDONLY, 0660)
Out[2]: 5

In [3]: os.fdopen(5, "rb")
---------------------------------------------------------------------------
IOError                                   Traceback (most recent call last)
<ipython-input-3-3ca4d619250e> in <module>()
----> 1 os.fdopen(5, "rb")

IOError: [Errno 21] Is a directory: '<fdopen>'

In [4]: os.close(5)
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-4-76713e571514> in <module>()
----> 1 os.close(5)

OSError: [Errno 9] Bad file descriptor

看起来 os.fdopen() 不仅消耗了我的文件描述符 5,还抛出了一个异常……

有没有什么安全的方法来使用 os.fdopen() 呢?

我是不是漏掉了什么?

我发现了一个bug吗?

附注:Python版本是 Python 2.7.6 (v2.7.6:3a1db0d2747e, Nov 10 2013, 00:42:54),以防有人在自己的环境中无法重现这个问题。

再附注:在Py2.7的Linux上也存在同样的问题。不过在Py3.3上就没有这个问题。

1 个回答

2

在创建Python文件对象并将其存储在Python对象中后,Python会检查这个FILE*是否指向一个目录。如果检查出是目录,就会出现错误。由于这个错误,文件对象会被解除引用(因为它不会被返回),这就导致了析构函数被调用,从而关闭了文件。

我同意,如果文档能说明这对传入的文件描述符有什么影响,那会很好。我不太确定你想要的“安全”使用fdopen的方式是什么。如果你打算在失败时关闭文件描述符,那Python关闭它又有什么关系呢?直接使用

try: os.close(fd)
except: pass

来处理第二个异常就可以了。

fill_file_fields函数是由PyFile_FromFile调用的,用来填充文件对象的各个成员。它在填充完字段后会调用dircheck函数。这个调用会导致fill_file_fields返回NULL,因此PyFile_FromFile会执行Py_DECREF(f);,其中f是文件对象。由于这是最后一个引用,内存释放器file_dealloc会被调用,这会触发close_the_file,结果(没想到吧)就是关闭了文件。

在3.4版本中,目录检查是在fileio_init中进行的,它使用一个标志变量fd_is_own来判断在出现错误时是否应该关闭文件。

撰写回答