os.fdopen() 的语义是什么?
我以前以为 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 个回答
在创建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
来判断在出现错误时是否应该关闭文件。