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 BTW。

4

3 回答 3

62

使用打开文件作为迭代器使用预读缓冲区来提高效率。因此,当您遍历行时,文件指针会在文件中大步前进。

文件对象文档中:

为了使 for 循环成为循环文件行的最有效方式(一种非常常见的操作),该next()方法使用隐藏的预读缓冲区。作为使用预读缓冲区的结果,next()与其他文件方法(如readline())组合不能正常工作。但是,使用seek()将文件重新定位到绝对位置将刷新预读缓冲区。

如果您需要依赖.tell(),请不要将文件对象用作迭代器。您可以.readline()改为使用迭代器(以牺牲一些性能为代价):

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

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

于 2013-01-03T18:41:31.503 回答
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()它的行为方式。

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

于 2013-01-03T18:44:12.560 回答
0

我遇到了相同的预读缓冲区问题,并使用Martijn 的建议解决了它。

从那以后,我将我的解决方案推广给其他想要做这些事情的人:

https://github.com/loisaidasam/csv-position-reader

快乐的 CSV 解析!

于 2018-08-10T19:23:14.327 回答