【问题标题】:Why does one file object flush, but the other one doesn't?为什么一个文件对象刷新,而另一个不刷新?
【发布时间】:2012-09-19 14:07:03
【问题描述】:

我想要一个在写入数据时直接刷新到文件的文件对象,并写了这个:

class FlushingFileObject(file):
    def write(self,*args,**kwargs):
        return_val= file.write(self,*args,**kwargs)
        self.flush()
        return return_val

    def writelines(self,*args,**kwargs):
        return_val= file.writelines(self,*args,**kwargs)
        self.flush()
        return return_val

但有趣的是,我写它时它并没有刷新,所以我尝试了一些方法,包括:

class FlushingFileObject(object):
    def __init__(self,*args,**kwargs):
        self.file_object= file(*args,**kwargs)

    def __getattr__(self, item):
        return getattr(self.file_object,item)

    def write(self,*args,**kwargs):
        return_val= self.file_object.write(*args,**kwargs)
        self.file_object.flush()
        return return_val

    def writelines(self,*args,**kwargs):
        return_val= self.file_object.writelines(*args,**kwargs)
        self.file_object.flush()
        return return_val

确实冲洗。

为什么子类化file 在这种情况下不起作用?

【问题讨论】:

  • 您能展示一下如何测试冲洗吗?因为当我尝试它们时两者都可以正常工作。
  • 我实际上将它们分配给 sys.stdoutsys.stderr 并运行我拥有的程序,并跟踪文件。对于第一个子类文件,只有在我关闭程序时才会写入输出
  • 也是一件奇怪的事情,它找不到我不明白的第二个对象的__exit__ 属性?当我尝试将它与with 一起使用时,我得到一个AttributeError
  • __exit__ 是代码退出with statement 时运行时自动调用的函数。为什么它找不到它,我不知道,因为它应该调用你的__getattr__,这反过来又会调用文件对象的__exit__。你运行的是什么版本的 Python?
  • @Adam 我正在运行 python 2.7.3-是的,所有其他属性似乎都被转发(甚至 __enter__)但由于某种原因没有退出

标签: python file flush


【解决方案1】:

好问题。

发生这种情况是因为 Python 通过绕过 Python 级别的 write 方法并直接调用 fputs 来优化对 file 对象上的 write 的调用。

要查看实际情况,请考虑:

$ cat file_subclass.py
import sys
class FileSubclass(file):
    def write(self, *a, **kw):
        raise Exception("write called!")
    writelines = write
sys.stdout = FileSubclass("/dev/null", "w")
print "foo"
sys.stderr.write("print succeeded!\n")
$ python print_magic.py
print succeeded!

从未调用过write 方法!

现在,当对象不是 file 的子类时,一切都会按预期工作:

$ cat object_subclass.py
import sys
class ObjectSubclass(object):
    def __init__(self):
        pass
    def write(self, *a, **kw):
        raise Exception("write called!")
    writelines = write
sys.stdout = ObjectSubclass()
print "foo"
sys.stderr.write("print succeeded!\n")
$ python object_subclass.py
Traceback (most recent call last):
  File "x.py", line 13, in <module>
    print "foo"
  File "x.py", line 8, in write
    raise Exception("write called!")
Exception: write called!

稍微挖掘一下 Python 源代码,看起来罪魁祸首是 PyFile_WriteString 函数,由 print 语句调用,它检查正在写入的对象是否是 file 的实例,并且如果是,则绕过对象的方法,直接调用fputs

int
PyFile_WriteString(const char *s, PyObject *f)
{

    if (f == NULL) {
        /* … snip … */
    }
    else if (PyFile_Check(f)) { //-- `isinstance(f, file)`
        PyFileObject *fobj = (PyFileObject *) f;
        FILE *fp = PyFile_AsFile(f);
        if (fp == NULL) {
            err_closed();
            return -1;
        }
        FILE_BEGIN_ALLOW_THREADS(fobj)
        fputs(s, fp); //-- fputs, bypassing the Python object entirely
        FILE_END_ALLOW_THREADS(fobj)
        return 0;
    }
    else if (!PyErr_Occurred()) {
        PyObject *v = PyString_FromString(s);
        int err;
        if (v == NULL)
            return -1;
        err = PyFile_WriteObject(v, f, Py_PRINT_RAW);
        Py_DECREF(v);
        return err;
    }
    else
        return -1;
}

【讨论】:

  • 我试图查看 python 源代码,但我无法找到“print”语句的确切定义位置。无论如何,我可以看到PyObject_Print 使用FILE 指针,所以也许当 print 看到文件子类时,它只是从结构中获取FILE 指针并使用它的写操作(因此它不调用被覆盖的方法) .
  • 参见ceval.c 中的case PRINT_ITEM:(版本2.7.2 中的第1752 行)。但是,是的,您是绝对正确的——详情请参阅我的编辑
  • 在第一个示例中,您覆盖了__init__,这会阻止底层file 类正确初始化。如果stdout 成功通过“文件实例”测试,则print 语句硬连线调用file.write 实现。反过来又会引发ValueError,因为您的子类对象未正确初始化。
  • @patrys 是的,完全正确。虽然……我想这个例子可能更明显。让我解决这个问题。
  • 很好的答案:)。这解释了很多谢谢!我想这都是以 io 优化的名义,但看到 2 个几乎相同的对象表现不同真的很令人困惑
【解决方案2】:

file.flush() 的文档说:

注意 flush() 不一定将文件的数据写入磁盘。使用 flush() 后跟 os.fsync() 来确保这种行为。

我在没有调用 os.fsync 的情况下测试了 FlushingFileObject 的第一个版本。该文件没有刷新。插入os.fsync(self.fileno()) 后,文件被刷新。然后我删除了 os.fsync 调用,现在文件被刷新了!我想可以肯定的是,os.fsync 调用是必要的。

【讨论】:

  • 您引用的文档并不正确,但这是对问题的错误答案。看我的回答。
猜你喜欢
  • 2013-02-19
  • 2012-06-12
  • 1970-01-01
  • 1970-01-01
  • 2014-10-31
  • 2021-11-03
  • 2023-04-09
  • 2015-08-13
  • 1970-01-01
相关资源
最近更新 更多