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, кстати.


person nigp4w rudy    schedule 03.01.2013    source источник
comment
просто освещая необходимый вопрос: вы уверены, что не дошли до конца файла в первом примере?   -  person Inbar Rose    schedule 03.01.2013


Ответы (3)


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

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

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

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

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

При этом используется аргумент iter() sentinel для превращения любого вызываемого объекта в итератор.

person Martijn Pieters    schedule 03.01.2013
comment
Просто чтобы добавить к этому, если вы знаете, с чего начался файл (например, с 0 или предыдущего seek(), вы можете просто отслеживать позицию файла вручную вместо использования tell(). Просто увеличивайте счетчик на длину каждой строки, которую вы чтение из next() Теперь у вас есть "правильная" позиция в файле и прирост производительности от упреждающего чтения. - person Tom Dalton; 17.02.2015
comment
@TomDalton: вы не можете сделать это на платформе Windows, так как разделители строк переводятся. Чтение строки дает вам x символов для каждого x + 1 байта на диске. Это также не работает при использовании io.open(), где многобайтовые символы на диске декодируются в одну кодовую точку Unicode. К счастью, файловые объекты io.open() не нуждаются в обходном пути, представленном в этом ответе. - person Martijn Pieters; 17.02.2015
comment
@MartijnPieters. Бессовестная самореклама: stackoverflow.com/q/48055409/2988730. Это та же самая причина, по которой tell был полностью отключен в Py3 на итерации? - person Mad Physicist; 02.01.2018
comment
@MadPhysicist: нет, причины для Python 3 разные; обертки TextIO* полагаются на несколько уровней буферизации, и поддержка Tell может иметь серьезные последствия для производительности. - person Martijn Pieters; 02.01.2018

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

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

person NPE    schedule 03.01.2013
comment
У вас есть подобное представление о Python3? stackoverflow.com/q/48055409/2988730 - person Mad Physicist; 02.01.2018

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

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

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

Удачного разбора CSV!

person Loisaida Sam Sandberg    schedule 10.08.2018