文件.tell()不一致性

40

有人知道为什么当你以这种方式迭代文件时:

输入:

f = open('test.txt', 'r')
for line in f:
    print "f.tell(): ",f.tell()

输出:

f.tell(): 8192
f.tell(): 8192
f.tell(): 8192
f.tell(): 8192

我不断地从tell()获取错误的文件索引,但如果我使用readline,我会得到适当的tell()索引:

输入:

f = open('test.txt', 'r')
while True:
    line = f.readline()
    if (line == ''):
        break
    print "f.tell(): ",f.tell()

输出:

f.tell(): 103
f.tell(): 107
f.tell(): 115
f.tell(): 124

顺便说一下,我正在运行 Python 2.7.1。


1
您确定您在第一个示例中没有到达文件结尾吗?只回答必需的问题。 - Inbar Rose
3个回答

64

使用打开的文件作为迭代器可以使用前置读取缓冲区来增加效率。因此,当您循环遍历行时,文件指针会跨越文件大步前进。

文件对象文档中:

为了使for循环成为循环文件行的最有效方式(非常常见的操作),next()方法使用隐藏的前置读取缓冲区。由于使用前置读取缓冲区,将next()与其他文件方法(例如readline())组合使用不起作用。但是,使用seek()将文件重新定位到绝对位置将刷新前置读取缓冲区。

如果您需要依赖.tell(),请不要将文件对象用作迭代器。相反,您可以将.readline()转换为迭代器(以一些性能损失为代价):

for line in iter(f.readline, ''):
    print f.tell()

使用iter()函数sentinel参数将任何可调用对象转换为迭代器。


2
补充一下,如果你知道文件从哪里开始(例如从0或之前的seek()),你可以手动跟踪文件位置而不是使用tell()。只需通过从next()读取的每行长度递增计数器即可。现在你有了一个“正确”的文件位置和来自预读的性能提升。 - Tom Dalton
5
@TomDalton:在Windows平台上无法做到这一点,因为换行符会被转换。在那里读取一行文本时,每x+1字节在磁盘上会得到x个字符。如果使用io.open(),磁盘上的多字节字符将被解码为一个Unicode代码点,这也不起作用。幸运的是,io.open()文件对象不需要本答案中提供的解决方法。 - Martijn Pieters
1
@MartijnPieters。不要脸的自我宣传:https://stackoverflow.com/q/48055409/2988730。这是`tell`在Py3迭代中完全被禁用的原因吗? - Mad Physicist
1
@MadPhysicist:不,Python 3的原因是不同的;TextIO*包装器依赖于多层缓冲和支持tell将会有严重的性能影响。 - Martijn Pieters

12

答案位于Python 2.7源代码的以下部分(fileobject.c):

#define READAHEAD_BUFSIZE 8192

static PyObject *
file_iternext(PyFileObject *f)
{
    PyStringObject* l;

    if (f->f_fp == NULL)
        return err_closed();
    if (!f->readable)
        return err_mode("reading");

    l = readahead_get_line_skip(f, 0, READAHEAD_BUFSIZE);
    if (l == NULL || PyString_GET_SIZE(l) == 0) {
        Py_XDECREF(l);
        return NULL;
    }
    return (PyObject *)l;
}

正如您所看到的,file的迭代器接口按8KB的块读取文件。这就解释了为什么f.tell()的行为是这样的。

文档建议出于性能原因这样做(并不保证预读缓冲区具有特定的大小)。


你是否有Python3类似的见解?https://stackoverflow.com/q/48055409/2988730 - Mad Physicist

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接