Как читать строки без перебора
У меня есть текстовый файл, и у меня есть условие, в котором мне нужно извлекать фрагмент текста через каждую строку, но фрагмент текста может быть любым количеством строк (файл FASTA, для любых людей, занимающихся биоинформатикой). Это в основном настроено так:
> header, info, info
TEXT-------------------------------------------------------
----------------------------------------------------
>header, info...
TEXT-----------------------------------------------------
... и так далее.
Я пытаюсь извлечь часть "ТЕКСТ". Вот код, который я настроил:
for line in ffile:
if line.startswith('>'):
# do stuff to header line
try:
sequence = ""
seqcheck = ffile.next() # line after the header will always be the beginning of TEXT
while not seqcheck.startswith('>'):
sequence += seqcheck
seqcheck = ffile.next()
except: # iteration error check
break
Это не работает, потому что каждый раз, когда я вызываю next(), он продолжает цикл for, в результате чего я пропускаю много строк и теряю много данных. Как я могу просто "заглянуть" в следующую строку, не перемещая итератор вперед?
5 ответов
Я думаю, если бы вы проверили, что данные не начинаются с '>'
было бы намного проще
>>> content = '''> header, info, info
... TEXT-------------------------------------------------------
... ----------------------------------------------------
... >header, info...
... TEXT-----------------------------------------------------'''
>>>
>>> f = StringIO(content)
>>>
>>> my_data = []
>>> for line in f:
... if not line.startswith('>'):
... my_data.append(line)
...
>>> ''.join(my_data)
'TEXT-------------------------------------------------------\n----------------------------------------------------\nTEXT-----------------------------------------------------'
>>>
Обновить:
@tobias_k это должно разделять строки:
>>> def get_content(f):
... my_data = []
... for line in f:
... if line.startswith('>'):
... yield my_data
... my_data = []
... else:
... my_data.append(line)
... yield my_data # the last on
...
>>>
>>> f.seek(0)
>>> for i in get_content(f):
... print i
...
[]
['TEXT-------------------------------------------------------\n', '----------------------------------------------------\n']
['TEXT-----------------------------------------------------']
>>>
Вы рассматривали регулярное выражение?:
txt='''\
> header, info, info
TEXT----------------------------------------------------------------
TEXT2-------------------------------------------
>header, info...
TEXT-----------------------------------------------------'''
import re
for header, data in ((m.group(1), m.group(2)) for m in re.finditer(r'^(?:(>.*?$)(.*?)(?=^>|\Z))', txt, re.S | re.M)):
# process header
# process data
print header, data
Это даст вам ваш заголовок и данные из этого заголовка в кортеже, чтобы сделать то, что вам нужно с ним сделать.
Если ваш файл огромен, вы можете использовать mmap, чтобы не читать весь файл в память.
Вот другой подход. Вопреки моему вышеупомянутому комментарию, он использует вложенный цикл для сбора всех строк, принадлежащих одному текстовому блоку (поэтому логика для этого не настолько разложена), но делает это немного по-другому:
for line in ffile:
if not line.startswith('>'):
sequence = line
for line in ffile:
if line.startswith('>'): break
sequence += line
print "<text>", sequence
if line.startswith('>'):
print "<header>", line
Во-первых, он использует второй for
цикл (используя тот же ffile
итератор как внешний цикл), поэтому нет необходимости try/except
, Во-вторых, ни одна строка не теряется, потому что мы подаем ток line
в sequence
и потому что мы сначала делаем случай без заголовка: во время второго if
проверка достигнута, line
переменная будет содержать строку заголовка, в которой вложенный цикл остановлен (не используйте else
здесь, или это не сработает).
Моя рекомендация для просмотра - использовать список и enumerate
:
lines = ffile.readlines()
for i, line in enumerate(lines):
if line.startswith('>'):
sequence = ""
for l in lines[i+1:]:
if l.startswith('>'):
break
sequence += l
Вот метод с очень небольшим изменением вашего исходного кода. Это зависит от вашей ситуации, но иногда проще просто делать то, что вы хотите делать, и не нужно беспокоиться о реорганизации / рефакторинге всего остального! Если вы хотите нажать что-то НАЗАД, чтобы это снова повторилось, просто сделайте это так, чтобы вы могли!
Здесь мы создаем экземпляр объекта deque(), который содержит ранее прочитанные строки. Затем мы оборачиваем итератор ffile, который выполняет простую проверку объекта и опустошает записи в нем, прежде чем получить новые строки из ffile.
Поэтому всякий раз, когда мы читаем что-то, что требует повторной обработки где-то еще, добавляем это к объекту deque и вырываемся.
import cStringIO,collections
original_ffile=cStringIO.StringIO('''
> header, info, info
TEXT----------------------------------------------------------------
TEXT2-------------------------------------------
>header, info...
TEXT-----------------------------------------------------''')
def peaker(_iter,_buffer):
popleft=_buffer.popleft
while True:
while _buffer: yield popleft() # this implements FIFO-style
yield next(_iter) # we don't have to catch StopIteration here!
buf=collections.deque()
push_back=buf.append
ffile=peaker(original_ffile,buf)
for line in ffile:
if line.startswith('>'):
print "found a header! %s"%line[:-1]
# do stuff to header line
sequence = ""
for seqcheck in ffile:
if seqcheck.startswith('>'):
print "oops, we've gone too far, pushing back: %s"%seqcheck[:-1]
push_back(seqcheck)
break
sequence += seqcheck
Выход:
found a header! > header, info, info
oops, we've gone too far, pushing back: >header, info...
found a header! >header, info...