Как решить «ошибка ОС: указание позиции отключено при следующем вызове ()»

#python

#python #file-io #обработка ошибок #Далее

Вопрос:

Я создаю систему редактирования файлов и хотел бы создать функцию tell () на основе строк вместо функции на основе байтов. Эта функция будет использоваться внутри цикла with с вызовом open (file). Эта функция является частью класса, который имеет:

 self.f = open(self.file, 'a ')
# self.file is a string that has the filename in it
  

Ниже приведена исходная функция
(У него также есть параметр символа, если вы хотите вернуть строку и байт):

 def tell(self, char=False):
    t, lc = self.f.tell(), 0
    self.f.seek(0)
    for line in self.f:
        if t >= len(line):
            t -= len(line)
            lc  = 1
        else:
            break
    if char:
        return lc, t
    return lc
  

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

Комментарии:

1. Трудно ответить, не видя остальных членов вашего класса. (Я не смог воспроизвести это в Linux, используя только функции.) Возможно, вы захотите ознакомиться с OSError атрибутами , которые могут дать вам (и нам) некоторую дополнительную информацию. Мой первый вопрос был бы, поскольку это ошибка операционной системы: какая у вас операционная система? Также (возможно, связано): почему / как вы открываете файл в режиме добавления , а затем seek перемещаетесь внутри него?

2. Я открываю его в режиме добавления, потому что предполагается, что файл не существует до создания экземпляра. (как вы знаете, я уверен, режим «a» создает файл, если он еще не существует). Я хотел иметь возможность сэкономить место в коде, чтобы проверить, существует ли файл. Моя операционная система — Mac OS X Yosemite, но я не думаю, что это имеет отношение к Apple.

Ответ №1:

Я не знаю, было ли это исходной ошибкой, но вы можете получить ту же ошибку, если попытаетесь вызвать f.tell() внутри построчной итерации файла, подобного so:

 with open(path, "r ") as f:
  for line in f:
    f.tell() #OSError
  

который можно легко заменить следующим:

 with open(path, mode) as f:
  line = f.readline()
  while line:
    f.tell() #returns the location of the next line
    line = f.readline()
  

Комментарии:

1. речь идет не о «внутри», а о том, происходило ли это раньше без промежуточного абсолютного поиска.

2. Отличное решение, спасибо!

3. Также, если вы используете достаточно современную версию Python, вы можете написать это без double, line = f.readline() заменив while line: на while line:= f.readline():

Ответ №2:

У меня более старая версия Python 3, и я на Linux вместо Mac, но я смог воссоздать что-то очень близкое к вашей ошибке:

 IOError: telling position disabled by next() call
  

Ошибка ввода, а не ошибка ОС, но в остальном то же самое. Как ни странно, я не мог вызвать это с помощью вашего open('a ', ...) , но только при открытии файла в режиме чтения: open('r ', ...) .

Еще одна путаница заключается в том, что ошибка исходит из _io.TextIOWrapper класса, который, по-видимому, определен в _pyio.py файле Python… Я подчеркиваю «появляется», потому что:

  1. TextIOWrapper В этом файле есть такие атрибуты, _telling к которым я не могу получить доступ к объекту, вызывающему сам себя, что бы это ни было _io.TextIOWrapper .

  2. TextIOWrapper Класс в _pyio.py не делает никаких различий между файлами для чтения, записи или произвольного доступа. Либо оба должны работать, либо оба должны вызывать одно и то же IOError .

Независимо от этого, TextIOWrapper класс, описанный в _pyio.py файле , отключает tell метод во время выполнения итерации. Похоже, это то, с чем вы столкнулись (комментарии мои):

 def __next__(self):
    # Disable the tell method.
    self._telling = False
    line = self.readline()
    if not line:
        # We've reached the end of the file...
        self._snapshot = None
        # ...so restore _telling to whatever it was.
        self._telling = self._seekable
        raise StopIteration
    return line
  

В вашем tell методе вы почти всегда break завершаете итерацию до того, как она достигает конца файла, оставляя _telling отключенным ( False ):

Еще одним способом сброса _telling является flush метод, но он также не удался, если вызывался во время выполнения итерации:

 IOError: can't reconstruct logical file position
  

Способ обойти это, по крайней мере, в моей системе, заключается в вызове seek(0) TextIOWrapper , который восстанавливает все до известного состояния (и успешно вызывает flush в придачу):

 def tell(self, char=False):
    t, lc = self.f.tell(), 0
    self.f.seek(0)
    for line in self.f:
        if t >= len(line):
            t -= len(line)
            lc  = 1
        else:
            break
    # Reset the file iterator, or later calls to f.tell will
    # raise an IOError or OSError:
    f.seek(0)
    if char:
        return lc, t
    return lc
  

Если это не решение для вашей системы, оно может, по крайней мере, подсказать вам, с чего начать поиск.

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

Комментарии:

1. Большое спасибо за вашу помощь! Похоже, моя проблема в том, что я не могу вызвать (встроенный) метод tell () во время итерации файла (построчно). Я нашел способ обойти это, и ваш ответ действительно помог. Еще раз спасибо!

2. @BrandonGomes: не могли бы вы поделиться со мной своим решением?

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

Ответ №3:

Просто быстрое решение этой проблемы:

Поскольку вы в любом случае выполняете итерацию по файлу с самого начала, просто следите за тем, где вы находитесь с выделенной переменной:

 file_pos = 0
with open('file.txt', 'rb') as f:
    for line in f:
        # process line
        file_pos  = len(line)
  

Теперь file_pos всегда будет, что file.tell() бы вам сказать. Обратите внимание, что это работает только для файлов ASCII, поскольку tell и seek работают с позициями байтов. Работая на основе строк, легко преобразовать строки из байта в строки Юникода.

Комментарии:

1. В py3 благодаря строке ‘rb’ это то, что вы ожидаете (включая терминаторы строк, как в rn ) — так что это отлично работает для перемотки к началу строки — отлично!

Ответ №4:

У меня была та же ошибка: ошибка OSError: указание позиции отключено при следующем вызове (), и я решил ее, добавив режим ‘rb’ при открытии файла.

Ответ №5:

Сообщение об ошибке довольно четкое, но отсутствует одна деталь: вызов next объекта текстового файла отключает tell метод. for Цикл многократно вызывает next iter(f) , который, оказывается, f сам по себе для файла. Я столкнулся с аналогичной проблемой, пытаясь вызвать tell внутри цикла вместо того, чтобы дважды вызывать вашу функцию.

Альтернативным решением является повторение файла без использования встроенного файлового итератора. Вместо этого вы можете создать почти столь же эффективный итератор из загадочной формы с двумя аргументами iter функции:

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