在os.rename()后更新文件描述符

1 投票
2 回答
3541 浏览
提问于 2025-04-16 21:32

在调用 os.rename() 之后,处理 Bad file descriptor 错误的最佳方法是什么?

f = open('foo.txt', 'rw')
os.rename(f.name, f.name + ".bak")

在文件系统中,已经没有 foo.txt 这个文件了,而是变成了 foo.txt.bak

但是……

f.name

却返回了 foo.txt 而不是 foo.txt.bak

可是……

f.write("test")

却出现了 Bad file descriptor 的错误

有没有好的方法来更新文件描述符呢?

即使文件已经被重命名,我还应该调用 f.close() 吗?

2 个回答

1

os.rename() 这个函数只根据文件的名字来工作,它并不知道有没有其他地方正在使用这个文件。所以在你对文件进行操作后,不能指望之前打开的文件对象还能正常使用。因此,最好在操作完成后把文件对象关闭,这样做是比较正确的选择。

4

你可以写一个函数来重命名一个打开的文件。基本上,这个过程是先关闭文件,然后重命名,再重新打开文件,同时保留一些属性,比如文件的位置和模式。不过在重新打开文件的时候,模式需要稍微调整一下。如果文件的模式是“w”,那么用同样的模式重新打开会把文件里的内容全部清空,所以在这种情况下,我们会用“r+”模式来重新打开。虽然这样做并不完美,因为它允许读取文件,而之前是没有这个权限的,但这是我们能做到的最好办法。你当然会得到一个全新的 file 对象,这就是函数的返回值。

import os

def rename_open_file(fileobj, newname):
    name = fileobj.name
    mode = fileobj.mode.lower()
    posn = fileobj.tell()
    fileobj.close()
    os.rename(name, newname)
    newmode = mode
    if "w" in mode:      # can't reopen with "w" mode since
        newmode = "r+"   # it would empty the file; use "r+"
        if "b" in mode:
           newmode += "b"
    fileobj = open(name, newmode)
    fileobj.seek(posn)
    return fileobj

f = rename_open_file(f, f.name + ".bak")

如果你有多个 file 对象在引用这个打开的文件,那就没什么用处了;其他的引用可能会失效。

需要注意的是,文件的 name 属性不一定是完整的路径,所以如果你是用相对路径打开文件的,而在打开文件后又改变了目录,这样就行不通了。如果这是个问题,你可以写自己的 open() 函数,在打开文件时使用 os.path.abspath() 来获取文件的完整路径。

另外,打开文件时给定的缓冲区大小也不会被保留,因为这个信息并没有记录在文件对象上。自己写一个 open() 函数也可以解决这个问题。最简单的方法是继承 file 类。

from os.path import abspath
class open(file):
    def __init__(self, filename, mode="r", buffering=-1):
        file.__init__(self, abspath(filename), mode, buffering)
        self.buffering = buffering

然后你可以在你的函数中添加对缓冲区的维护:

import os

def rename_open_file(fileobj, newname):
    name = fileobj.name
    mode = fileobj.mode.lower()
    posn = fileobj.tell()
    buff = fileobj.buffering
    fileobj.close()
    os.rename(name, newname)
    newmode = mode
    if "w" in mode:      # can't reopen with "w" mode since
        newmode = "r+"   # it would empty the file; use "r+"
        if "b" in mode:
           newmode += "b"
    fileobj = open(name, newmode, buff)
    fileobj.seek(posn)
    return fileobj

你也可以为文件对象写一个 包装类,而不是直接继承 file,让它将所有 file 的方法调用传递给被包装的对象。这样 rename() 就可以成为这个包装类的方法,完成上面的所有操作。因为调用代码会保持对包装类的引用,所以它不需要知道底层的 file 对象已经不同了。我就把这个留给你自己去练习吧。 :-)

撰写回答