Python file.tell дает неправильное местоположение значения

Я пытаюсь извлечь несколько мест из существующего файла с помощью Python. Это мой текущий код для извлечения местоположений:

    self.fh = open( fileName , "r+")
    p = re.compile('regGen regPorSnip begin')
    for line in self.fh :
        if ( p.search(line) ):
            self.porSnipStartFPtr = self.fh.tell()
            sys.stdout.write("found regPorSnip")

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

Однако, используя приведенный ниже код, первое место записи неверно, а последующие места записи верны:

    self.fh.seek(self.rstSnipStartFPtr,0)
    self.fh.write(str);
    sys.stdout.write("writing %s" % str )
    self.rstSnipStartFPtr = self.fh.tell()

Я читал, что передача определенных параметров read/readline в fh может привести к ошибочному значению сообщения из-за склонности Python к «упреждающему чтению». Одно предложение, которое я видел, чтобы избежать этого, - прочитать весь файл и переписать его, что не очень привлекательное решение в моем приложении.

Если я изменю первый фрагмент кода на:

  for line in self.fh.read() :
        if ( p.search(line) ):
            self.porSnipStartFPtr = self.fh.tell()
            sys.stdout.write("found regPorSnip")

Затем оказывается, что self.fh.read() возвращает только символы, а не всю строку. Поиск никогда не совпадает. То же самое относится и к self.fh.readline().

Я пришел к выводу, что fh.tell возвращает действительные местоположения файлов только при запросе после операции записи.

Есть ли способ извлечь точное местоположение файла при чтении/поиске?

Спасибо.


person ktom    schedule 01.11.2013    source источник
comment
К вашему сведению: stackoverflow.com/a/15935038/8747   -  person Robᵩ    schedule 01.11.2013


Ответы (2)


Причина (довольно неясно) объясняется в документации для метода next() файлового объекта:

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

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

Там нет портативного способа обойти это. Если вам нужно смешать tell() с чтением строк, используйте вместо этого метод файла readline(). Компромисс заключается в том, что в обмен на получение пригодных для использования результатов tell() перебор большого файла с readline() обычно происходит значительно медленнее, чем с использованием for line in file_object:.

Код

Конкретно, измените цикл на это:

line = self.fh.readline()
while line:
    if p.search(line):
        self.porSnipStartFPtr = self.fh.tell()
        sys.stdout.write("found regPorSnip")
    line = fh.readline()

Я не уверен, что вы действительно хотите: tell() захватывает позицию начала следующей строки. Если вам нужна позиция начала строки, вам нужно изменить логику, например:

pos = self.fh.tell()
line = self.fh.readline()
while line:
    if p.search(line):
        self.porSnipStartFPtr = pos
        sys.stdout.write("found regPorSnip")
    pos = self.fh.tell()
    line = fh.readline()

или сделать это с помощью «полуторного цикла»:

while True:
    pos = self.fh.tell()
    line = self.fh.readline()
    if not line:
        break
    if p.search(line):
        self.porSnipStartFPtr = pos
        sys.stdout.write("found regPorSnip")
person Tim Peters    schedule 01.11.2013
comment
Файл невелик, поэтому штраф за использование readline, я не думаю, будет проблемой. первый вариант является наиболее подходящим. начало следующей строки в порядке. кажется, что проверка на пустой файл при чтении строки не может быть выполнена, когда требуется указатель файла. Благодарю за разъяснение. Очень ценится. - person ktom; 01.11.2013
comment
Фантастическое объяснение! Большое спасибо! Я также обнаружил эту проблему при обработке большого файла, но решил ее, сохранив переменную смещения вручную (смещение += len(line)) вместо вызова fh.tell(). Таким образом, вы можете сохранить оптимизации, включенные в next(). - person dugloon; 04.04.2017
comment
@dugloon, это должно работать в системах Linux, но результаты tell() для файлов текстового режима в Windows обычно не являются простыми смещениями байтов в файле. Python наследует это ограничение от C. Вот почему в документации говорится, что в текстовых файлах (которые открываются без b в строке режима) разрешены только поиски относительно начала файла (за исключением поиска до самого конца файла с помощью seek( 0, 2)) и единственными допустимыми значениями смещения являются те, которые возвращаются из f.tell(), или ноль. Любое другое значение смещения приводит к неопределенному поведению. - person Tim Peters; 04.04.2017
comment
Спасибо Тим! Я забыл добавить этот кусок - я открываю с помощью режима = rb - person dugloon; 05.04.2017

видимо я не понимаю вопроса

>>> fh = open('test.txt')
>>> fh.tell()
0L
>>> fh.read(1)
'"'
>>> fh.tell()
1L
>>> fh.read(5)
'a" \n"'
>>> fh.tell()
7L
person Joran Beasley    schedule 01.11.2013
comment
На самом деле проблема связана с использованием for line in file_object: - тогда есть еще один уровень буферизации. - person Tim Peters; 01.11.2013
comment
ааа понял ... хорошо я удалю это - person Joran Beasley; 01.11.2013
comment
почему for line in file_object (питоновский способ) является проблемой? - person iacopo; 14.03.2014