#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… Я подчеркиваю «появляется», потому что:
-
TextIOWrapper
В этом файле есть такие атрибуты,_telling
к которым я не могу получить доступ к объекту, вызывающему сам себя, что бы это ни было_io.TextIOWrapper
. -
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, ''):