Уменьшить счетчик в итерируемом, полученном с использованием 'enumerate' после поиска вызова
Я читаю файл, используя Python, и в нем есть разделы, которые заключены в символ "#":
#HEADER1, SOME EXTRA INFO
data first section
1 2
1 233
...
// THIS IS A COMMENT
#HEADER2, SECOND SECTION
452
134
// ANOTHER COMMENT
...
#HEADER3, THIRD SECTION
Теперь я написал код для чтения файла следующим образом:
with open(filename) as fh:
enumerated = enumerate(iter(fh.readline, ''), start=1)
for lino, line in enumerated:
# handle special section
if line.startswith('#'):
print("="*40)
print(line)
while True:
start = fh.tell()
lino, line = next(enumerated)
if line.startswith('#'):
fh.seek(start)
break
print("[{}] {}".format(lino,line))
Выход:
========================================
#HEADER1, SOME EXTRA INFO
[2] data first section
[3] 1 2
[4] 1 233
[5] ...
[6] // THIS IS A COMMENT
========================================
#HEADER2, SECOND SECTION
[9] 452
[10] 134
[11] // ANOTHER COMMENT
[12] ...
========================================
#HEADER3, THIRD SECTION
Теперь вы видите, что счетчик строки lino
больше не действителен, потому что я использую seek
, Кроме того, это не поможет уменьшить его перед разрывом цикла, потому что этот счетчик увеличивается с каждым вызовом next
, Так есть ли элегантный способ решить эту проблему в Python 3.x? Кроме того, есть ли лучший способ решения StopIteration
без сдачи pass
заявление в Except
блок?
ОБНОВИТЬ
До сих пор я принял реализацию, основанную на предложении @Dunes. Мне пришлось немного изменить его, чтобы я мог заглянуть вперед, чтобы увидеть, начинается ли новый раздел. Я не знаю, есть ли лучший способ сделать это, поэтому, пожалуйста, прыгайте с комментариями:
Класс EnumeratedFile:
def __init__(self, fh, lineno_start=1):
self.fh = fh
self.lineno = lineno_start
def __iter__(self):
return self
def __next__(self):
result = self.lineno, self.fh.readline()
if result[1] == '':
raise StopIteration
self.lineno += 1
return result
def mark(self):
self.marked_lineno = self.lineno
self.marked_file_position = self.fh.tell()
def recall(self):
self.lineno = self.marked_lineno
self.fh.seek(self.marked_file_position)
def section(self):
pos = self.fh.tell()
char = self.fh.read(1)
self.fh.seek(pos)
return char != '#'
А затем файл читается и каждый раздел обрабатывается следующим образом:
# create enumerated object
e = EnumeratedFile(fh)
header = ""
for lineno, line, in e:
print("[{}] {}".format(lineno, line))
header = line.rstrip()
# HEADER1
if header.startswith("#HEADER1"):
# process header 1 lines
while e.section():
# get node line
lineno, line = next(e)
# do whatever needs to be done with the line
elif header.startswith("#HEADER2"):
# etc.
2 ответа
Вы не можете изменить счетчик enumerate()
повторяемый, нет.
Вам не нужно здесь вообще, и вам не нужно искать. Вместо этого используйте вложенный цикл и буферизуйте заголовок раздела:
with open(filename) as fh:
enumerated = enumerate(fh, start=1)
header = None
for lineno, line in enumerated:
# seek to first section
if header is None:
if not line.startswith('#'):
continue
header = line
print("=" * 40)
print(header.rstrip())
for lineno, line in enumerated:
if line.startswith('#'):
# new section
header = line
break
# section line, handle as such
print("[{}] {}".format(lineno, line.rstrip()))
Это буферизует только строку заголовка; каждый раз, когда мы сталкиваемся с новым заголовком, он сохраняется и текущий цикл раздела заканчивается.
Демо-версия:
>>> from io import StringIO
>>> demo = StringIO('''\
... #HEADER1, SOME EXTRA INFO
... data first section
... 1 2
... 1 233
... ...
... // THIS IS A COMMENT
... #HEADER2, SECOND SECTION
... 452
... 134
... // ANOTHER COMMENT
... ...
... #HEADER3, THIRD SECTION
... ''')
>>> enumerated = enumerate(demo, start=1)
>>> header = None
>>> for lineno, line in enumerated:
... # seek to first section
... if header is None:
... if not line.startswith('#'):
... continue
... header = line
... print("=" * 40)
... print(header.rstrip())
... for lineno, line in enumerated:
... if line.startswith('#'):
... # new section
... header = line
... break
... # section line, handle as such
... print("[{}] {}".format(lineno, line.rstrip()))
...
========================================
#HEADER1, SOME EXTRA INFO
[2] data first section
[3] 1 2
[4] 1 233
[5] ...
[6] // THIS IS A COMMENT
========================================
#HEADER2, SECOND SECTION
[9] 134
[10] // ANOTHER COMMENT
[11] ...
>>> header
'#HEADER3, THIRD SECTION\n'
Третий раздел остается необработанным, потому что в нем не было ни одной строки, но если бы header
переменная уже была установлена в ожидании.
Вы можете скопировать итератор, а затем восстановить итератор из этой копии. Тем не менее, вы не можете скопировать файл объекта. Вы можете взять мелкую копию перечислителя, а затем начать поиск соответствующей части файла, когда начнете использовать скопированный перечислитель.
Однако лучше всего написать класс генератора с __next__
способ получения номеров строк и строк, и mark
а также recall
методы для записи и возврата в ранее записанное состояние.
class EnumeratedFile:
def __init__(self, fh, lineno_start=1):
self.fh = fh
self.lineno = lineno_start
def __iter__(self):
return self
def __next__(self):
result = self.lineno, next(self.fh)
self.lineno += 1
return result
def mark(self):
self.marked_lineno = self.lineno
self.marked_file_position = self.fh.tell()
def recall(self):
self.lineno = self.marked_lineno
self.fh.seek(self.marked_file_position)
Вы бы использовали это так:
from io import StringIO
demo = StringIO('''\
#HEADER1, SOME EXTRA INFO
data first section
1 2
1 233
...
// THIS IS A COMMENT
#HEADER2, SECOND SECTION
452
134
// ANOTHER COMMENT
...
#HEADER3, THIRD SECTION
''')
e = EnumeratedFile(demo)
seen_header2 = False
for lineno, line, in e:
if seen_header2:
print(lineno, line)
assert (lineno, line) == (2, "data first section\n")
break
elif line.startswith("#HEADER1"):
e.mark()
elif line.startswith("#HEADER2"):
e.recall()
seen_header2 = True