Трансляция: от мРНК к белку

Трансляция — превращение строки мРНК в строку белка по таблице кодонов. Один из самых наглядных алгоритмов курса.

Трансляция — синтез белка по мРНК: рибосома читает кодоны от старт-сигнала до стоп-сигнала и собирает цепочку аминокислот.

Имея таблицу кодонов из прошлого урока, написать трансляцию — дело нескольких строк. Но в деталях кроется биология: где начать, где остановиться, что делать с «хвостом». Разберём по шагам и получим настоящий белок.

Наивная трансляция всей строки

Простейший вариант: бить мРНК на тройки с начала и переводить каждую, останавливаясь на стоп-кодоне.

CODON = {
    'UUU':'F','UUC':'F','UUA':'L','UUG':'L','CUU':'L','CUC':'L','CUA':'L','CUG':'L',
    'AUU':'I','AUC':'I','AUA':'I','AUG':'M','GUU':'V','GUC':'V','GUA':'V','GUG':'V',
    'UCU':'S','UCC':'S','UCA':'S','UCG':'S','CCU':'P','CCC':'P','CCA':'P','CCG':'P',
    'ACU':'T','ACC':'T','ACA':'T','ACG':'T','GCU':'A','GCC':'A','GCA':'A','GCG':'A',
    'UAU':'Y','UAC':'Y','UAA':'*','UAG':'*','CAU':'H','CAC':'H','CAA':'Q','CAG':'Q',
    'AAU':'N','AAC':'N','AAA':'K','AAG':'K','GAU':'D','GAC':'D','GAA':'E','GAG':'E',
    'UGU':'C','UGC':'C','UGA':'*','UGG':'W','CGU':'R','CGC':'R','CGA':'R','CGG':'R',
    'AGU':'S','AGC':'S','AGA':'R','AGG':'R','GGU':'G','GGC':'G','GGA':'G','GGG':'G',
}

def translate(rna):
    protein = ''
    for i in range(0, len(rna) - 2, 3):
        aa = CODON[rna[i:i+3]]
        if aa == '*':
            break
        protein += aa
    return protein

rna = 'AUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAG'
print('мРНК: ', rna)
print('Белок:', translate(rna))

Вывод:

мРНК:  AUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAG
Белок: MAIVMGR

Разбор: range(0, len-2, 3) идёт по тройкам и не вылезает за конец; на стоп-кодоне (UGA здесь) перевод обрывается, поэтому белок короче, чем «вся строка делённая на 3».

Трансляция из ДНК напрямую

На практике у нас обычно ДНК, а не мРНК. Можно завести ДНК-таблицу (с T) или транскрибировать. Покажем перевод прямо из ДНК — удобно, потому что геномы хранятся в ДНК.

DNA_CODON = {
    'TTT':'F','TTC':'F','TTA':'L','TTG':'L','CTT':'L','CTC':'L','CTA':'L','CTG':'L',
    'ATT':'I','ATC':'I','ATA':'I','ATG':'M','GTT':'V','GTC':'V','GTA':'V','GTG':'V',
    'TCT':'S','TCC':'S','TCA':'S','TCG':'S','CCT':'P','CCC':'P','CCA':'P','CCG':'P',
    'ACT':'T','ACC':'T','ACA':'T','ACG':'T','GCT':'A','GCC':'A','GCA':'A','GCG':'A',
    'TAT':'Y','TAC':'Y','TAA':'*','TAG':'*','CAT':'H','CAC':'H','CAA':'Q','CAG':'Q',
    'AAT':'N','AAC':'N','AAA':'K','AAG':'K','GAT':'D','GAC':'D','GAA':'E','GAG':'E',
    'TGT':'C','TGC':'C','TGA':'*','TGG':'W','CGT':'R','CGC':'R','CGA':'R','CGG':'R',
    'AGT':'S','AGC':'S','AGA':'R','AGG':'R','GGT':'G','GGC':'G','GGA':'G','GGG':'G',
}

def translate_dna(dna):
    protein = ''
    for i in range(0, len(dna) - 2, 3):
        aa = DNA_CODON[dna[i:i+3]]
        if aa == '*':
            break
        protein += aa
    return protein

gene = 'ATGTTTAAAGGGTGCCCGTAA'
print('Ген:  ', gene)
print('Белок:', translate_dna(gene))

Вывод:

Ген:   ATGTTTAAAGGGTGCCCGTAA
Белок: MFKGCP

Как работает под капотом: рамка считывания

Один и тот же текст ДНК даёт разные белки в зависимости от того, с какой позиции начать делить на тройки. Эти три варианта (старт 0, 1, 2) — три рамки считывания. Сдвиг на одну букву (frameshift-мутация, вставка или удаление одной буквы) полностью меняет белок ниже точки сдвига — поэтому такие мутации часто катастрофичны. Проверим, как меняется перевод при сдвиге старта.

DNA_CODON = {'ATG':'M','TTT':'F','TGA':'*','GAT':'D','GTT':'V','ATT':'I','TTG':'L'}
def translate_from(dna, start):
    out = ''
    for i in range(start, len(dna) - 2, 3):
        aa = DNA_CODON.get(dna[i:i+3], '?')
        if aa == '*':
            break
        out += aa
    return out

dna = 'ATGTTTGA'
for frame in range(3):
    print(f'рамка {frame}:', translate_from(dna, frame))

Вывод:

рамка 0: MF
рамка 1: ?L
рамка 2: V

Три рамки дали три совершенно разных результата: MF, ?L и V (вопрос — кодон не из нашего урезанного словаря). Это иллюстрирует, насколько выбор рамки решающий: одна и та же ДНК читается тремя способами, и лишь один обычно «настоящий». Поиск правильной рамки — тема следующего урока про ORF.

Здесь скрыт важный для медицины момент. Мутации, которые добавляют или удаляют число букв, не кратное трём, вызывают сдвиг рамки (frameshift), и весь белок ниже точки сдвига превращается в бессмыслицу — чаще всего вскоре натыкается на случайный стоп-кодон и обрывается. Поэтому вставка или удаление одной-двух букв обычно куда губительнее, чем замена одной буквы: замена меняет максимум одну аминокислоту, а сдвиг рамки уничтожает всю оставшуюся часть белка. Многие тяжёлые наследственные заболевания вызваны именно frameshift-мутациями. А вот вставка или удаление ровно трёх букв сохраняет рамку и просто добавляет или убирает одну аминокислоту — последствия обычно мягче. Эта арифметика «кратно ли трём» — то, что биоинформатик первым делом проверяет, оценивая инсерцию или делецию.

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

  • Не останавливаться на стопе. Без проверки на '*' вы переведёте мусор после конца белка.
  • Игнорировать неполный хвост. Если длина не кратна 3, последние 1–2 буквы — не кодон; range(0, len-2, 3) их отбрасывает.
  • Перепутать ДНК и РНК-таблицу. T против U — частый источник KeyError.

Итог

  • Трансляция — разбиение мРНК на кодоны и перевод по таблице до стоп-сигнала.
  • Удобно range(0, len-2, 3) для тройки и обрыв на '*'.
  • Транслировать можно прямо из ДНК, заведя таблицу с T.
  • Выбор рамки считывания критичен: сдвиг на одну букву меняет весь белок ниже точки сдвига.
Проверьте себя
1. На каком кодоне трансляция должна остановиться?
AНа AUG
BНа любом стоп-кодоне (UAA, UAG, UGA)
CВ конце строки всегда
DНа GGG
2. Почему в коде используют range(0, len(rna) - 2, 3)?
AЧтобы пропустить первый кодон
BЧтобы идти по тройкам и не выйти за конец строки на неполном хвосте
CЭто ускоряет цикл
DЧтобы перевести строку в РНК
3. Что происходит при сдвиге рамки считывания на одну букву?
AНичего не меняется
BМеняется только первая аминокислота
CПолностью меняется белок ниже точки сдвига
DБелок становится длиннее ровно на 1