Как читать строки без перебора

У меня есть текстовый файл, и у меня есть условие, в котором мне нужно извлекать фрагмент текста через каждую строку, но фрагмент текста может быть любым количеством строк (файл 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...
Другие вопросы по тегам