Чтение в файле блок за блоком, используя указанный разделитель в Python

У меня есть файл input_file.fa, ​​как это (FASTA формат):

> header1 description
data data
data
>header2 description
more data
data
data

Я хочу прочитать в файле один блок за раз, чтобы каждый блок содержал один заголовок и соответствующие данные, например, блок 1:

> header1 description
data data
data

Конечно, я мог бы просто прочитать в файле, как это и разделить:

with open("1.fa") as f:
    for block in f.read().split(">"):
        pass

НоЯ хочу избежать чтения всего файла в памятьпотому что файлы часто большие.

Я могу читать в файле строка за строкой, конечно:

with open("input_file.fa") as f:
    for line in f:
        pass

Но в идеале я хочу что-то вроде этого:

with open("input_file.fa", newline=">") as f:
    for block in f:
        pass

Но я получаю ошибку:

ValueError: недопустимое значение новой строки:>

Я также пытался использоватьмодуль CSV, но безуспешно.

Я нашелэта почта 3 года назад, который предоставляет решение этой проблемы на основе генератора, но это не кажется таким компактным, действительно ли это единственное / лучшее решение? Было бы здорово, если бы можно было создать генератор с одной строкой, а не с отдельной функцией, что-то вроде этого псевдокода:

with open("input_file.fa") as f:
    blocks = magic_generator_split_by_>
    for block in blocks:
        pass

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

 Chris_Rands29 июл. 2016 г., 12:13
@AshwiniChaudhary Спасибо, хорошая идея, это должно помочь в этом случае, но в идеале я также хотел бы общее решение, которое работало бы вне форматов данных биологической последовательности.
 Ashwini Chaudhary29 июл. 2016 г., 11:29
Вы пытались использоватьbiopython.org/wiki/Biopython?

Ответы на вопрос(3)

Решение Вопроса

Общее решение здесь будет написать функцию генератора для этого, которая дает одну группу за один раз. Это было то, что вы будете хранить только одну группу за один раз в памяти.

def get_groups(seq, group_by):
    data = []
    for line in seq:
        # Here the `startswith()` logic can be replaced with other
        # condition(s) depending on the requirement.
        if line.startswith(group_by):
            if data:
                yield data
                data = []
        data.append(line)

    if data:
        yield data

with open('input.txt') as f:
    for i, group in enumerate(get_groups(f, ">"), start=1):
        print ("Group #{}".format(i))
        print ("".join(group))

Выход:

Group #1
> header1 description
data data
data

Group #2
>header2 description
more data
data
data

Для форматов FASTA в целом, я бы рекомендовал использоватьBiopython пакет.

 Chris_Rands29 июл. 2016 г., 14:27
Хотя это все еще не совсем идея, которую я имел в виду, я думаю, что это хорошее практическое решение проблемы (лучше, чем в другом посте), поэтому я отмечу как решенное, спасибо за вашу помощь.
 Bakuriu29 июл. 2016 г., 18:19
Ты можешь изменитьсяdata = [line] сdata = [] и двигатьсяdata.append(line) вне внешнегоif, удаляяelseТаким образом, избегая двойного вызова.

Один подход, который мне нравится, это использоватьitertools.groupby вместе с простымkey фикция:

from itertools import groupby


def make_grouper():
    counter = 0
    def key(line):
        nonlocal counter
        if line.startswith('>'):
            counter += 1
        return counter
    return key

Используйте это как:

with open('filename') as f:
    for k, group in groupby(f, key=make_grouper()):
        fasta_section = ''.join(group)   # or list(group)

Вам нужноjoin только если вам нужно обрабатывать содержимое целого раздела как одну строку. Если вас интересует только чтение строк, вы можете просто сделать:

with open('filename') as f:
    for k, group in groupby(f, key=make_grouper()):
        # parse >header description
        header, description = next(group)[1:].split(maxsplit=1)
        for line in group:
            # handle the contents of the section line by line
 Chris_Rands29 июл. 2016 г., 23:29
Спасибо за ваш вдумчивый ответ. Я должен смотреть более внимательно, чтобы полностью понять код. Как вы думаете, есть ли у этого подхода особое преимущество перед другими?
 Bakuriu30 июл. 2016 г., 09:34
@Chris_Rands Этот подход в основном эквивалентен ответу Ашвини, но используетitertools чтобы избежать группирования линий вручную. Это вопрос вкуса.
def read_blocks(file):
    block = ''
    for line in file:
        if line.startswith('>') and len(block)>0:
            yield block
            block = ''
        block += line
    yield block


with open('input_file.fa') as f:
    for block in read_blocks(f):
        print(block)

Это будет читать файл в строке построчно, и вы получите обратно блоки с оператором yield. Это лениво, поэтому вам не нужно беспокоиться о большом объеме памяти.

Ваш ответ на вопрос