Несоответствие 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 Кстати.

3 ответа

Решение

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

Из документации файловых объектов:

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

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

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

Это использует iter() функция sentinel аргумент, чтобы превратить любой вызываемый в итератор.

Ответ лежит в следующей части исходного кода 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).

Я столкнулся с той же проблемой буфера для чтения и решил ее , предложив Мартина.

С тех пор я обобщил свое решение для тех, кто хочет делать такие вещи:

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

Счастливый разбор CSV!

Другие вопросы по тегам