Несоответствие 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!