Несоответствие file.tell()

Кто-нибудь знает, почему, когда вы перебираете файл таким образом:

Вход:

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.

Ответ 1

Использование открытых файлов в качестве итератора использует буфер чтения для повышения эффективности. В результате указатель файла продвигается большими шагами по файлу, когда вы перебираете строки.

В документации File Objects:

Чтобы сделать цикл for наиболее эффективным способом петли над строками файла (очень общая операция), метод next() использует скрытый буфер чтения. В результате использования буфера чтения, объединение next() с другими файловыми методами (например, readline()) не работает правильно. Однако использование seek() для перестановки файла в абсолютную позицию приведет к сбросу буфера чтения.

Если вам нужно полагаться на .tell(), не используйте файл-объект как итератор. Вместо этого вы можете превратить .readline() в итератор (по цене некоторой потери производительности):

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

Здесь используется аргумент iter() sentinel, чтобы превратить любое вызываемое в итератор.

Ответ 2

Ответ заключается в следующей части исходного кода 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 интерфейс итератора считывает файл в блоках по 8 КБ. Это объясняет, почему f.tell() ведет себя так, как это делает.

Документация предлагает ее по соображениям производительности (и не гарантирует какой-либо конкретный размер буфера readahead).