Формат FASTA: как хранят геномы

FASTA — простейший и самый распространённый текстовый формат для последовательностей. Разобрать его — обязательный навык.

FASTA — текстовый формат, где каждая запись состоит из строки-заголовка, начинающейся с символа «больше» (>), и следующих за ней строк с самой последовательностью.

Любой геном, который вы скачаете из GenBank или Ensembl, скорее всего, придёт в FASTA. Формат настолько прост, что писать парсер вручную — нормальная практика и отличное упражнение. Сделаем это и разберём подводные камни.

Как устроен FASTA

Запись = заголовок + тело. Заголовок начинается с > и содержит идентификатор и описание. Тело — последовательность, возможно разбитая на строки фиксированной ширины (часто 60 или 70 символов).

>seq1 пример гена
ATGCGTACGTTAGC
ATCGGCTAGCTAGC
>seq2 другой фрагмент
GGGCCCAAATTT

Обратите внимание: одна последовательность может занимать несколько строк — это перенос для читаемости, а не разделитель записей. Записи разделяются только новой строкой-заголовком с >.

Парсер FASTA на чистом Python

Алгоритм: идём по строкам; строка с > начинает новую запись, остальные строки добавляются к текущей последовательности.

fasta = """>seq1 пример гена
ATGCGTACGTTAGC
ATCGGCTAGCTAGC
>seq2 другой фрагмент
GGGCCCAAATTT"""

def parse_fasta(text):
    records = {}
    name = None
    for line in text.splitlines():
        line = line.strip()
        if not line:
            continue
        if line.startswith(">"):
            name = line[1:].split()[0]  # id до первого пробела
            records[name] = ""
        else:
            records[name] += line
    return records

for name, seq in parse_fasta(fasta).items():
    print(f"{name}: длина {len(seq)} -> {seq}")

Вывод:

seq1: длина 28 -> ATGCGTACGTTAGCATCGGCTAGCTAGC
seq2: длина 12 -> GGGCCCAAATTT

Двенадцать строк кода — и у нас есть рабочий парсер, склеивающий многострочные последовательности и берущий чистый id из заголовка.

Зачем нужна статистика по FASTA

Получив записи, обычно сразу считают сводку: число последовательностей, общую длину, GC-состав. Это первый sanity-check: совпадает ли размер с ожидаемым, не пустой ли файл.

records = {
    "seq1": "ATGCGTACGTTAGCATCGGCTAGCTAGC",
    "seq2": "GGGCCCAAATTT",
}
total = sum(len(s) for s in records.values())
gc = sum(s.count("G") + s.count("C") for s in records.values())
print("Записей:", len(records))
print("Суммарная длина:", total)
print(f"Общий GC-состав: {gc / total * 100:.1f}%")

Вывод:

Записей: 2
Суммарная длина: 40
Общий GC-состав: 52.5%

Как работает под капотом: почему не split по >

Соблазнительно сделать text.split(">"), но это хрупко: символ > теоретически может встретиться в описании, а пустые куски придётся отбрасывать. Построчный конечный автомат (заголовок/тело) надёжнее и работает потоково — можно читать гигантский файл, не загружая целиком в память.

Формат FASTA родился ещё в 1985 году как часть программы FASTA для поиска сходства последовательностей — отсюда и название. Его пережили десятки более «умных» форматов именно из-за простоты: это просто текст, который читается глазами, грепается, режется стандартными утилитами Unix. У формата есть негласные соглашения, о которых полезно знать. Идентификатор в заголовке часто кодирует источник: например, строка вида sp|P69905|HBA_HUMAN в базе UniProt означает запись из Swiss-Prot с номером P69905. Описание после id человекочитаемое и в анализе обычно игнорируется. Расширения файлов варьируются (.fasta, .fa, .fna для нуклеотидов, .faa для белков), но внутри это всё тот же формат.

Отдельно стоит сжатие. Геномные FASTA-файлы огромны, поэтому почти всегда хранятся сжатыми (обычно gzip, расширение .fasta.gz). На практике их читают «на лету» через модуль gzip стандартной библиотеки, не распаковывая на диск, — это экономит и место, и время. Наш построчный парсер работает с такими файлами без изменений: достаточно открыть файл через gzip.open(path, "rt") вместо обычного open, а дальше логика та же. Это ещё один довод в пользу потокового, а не загружающего-всё-в-память подхода.

Частые ошибки

  • Считать каждую строку тела отдельной последовательностью. Многострочное тело надо склеивать до следующего >.
  • Брать весь заголовок как id. Обычно id — это часть до первого пробела, остальное — описание.
  • Загружать огромный файл целиком. Для геномов читайте построчно (потоково).

Итог

  • FASTA — текстовый формат: строка-заголовок с > плюс одна или несколько строк последовательности.
  • Парсер строится как построчный автомат: > начинает запись, прочие строки добавляются к телу.
  • Id берут до первого пробела; тело склеивают из всех строк до следующего заголовка.
  • После парсинга считают сводку (число записей, длину, GC) как первичную проверку.
Проверьте себя
1. С какого символа начинается строка-заголовок записи в FASTA?
A@
B#
C>
D!
2. Последовательность в FASTA занимает несколько строк. Что это значит?
AЭто несколько разных последовательностей
BЭто одна последовательность, перенесённая для читаемости; склеиваем до следующего >
CФайл повреждён
DКаждая строка — отдельный ген
3. Почему построчный парсер надёжнее, чем text.split('>')?
AОн короче
BРаботает потоково и не ломается на > в описании или пустых кусках
Csplit вообще не работает в Python
DЭто единственный способ