-
Notifications
You must be signed in to change notification settings - Fork 0
Review ELMOD1 #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Review ELMOD1 #38
Conversation
Alisa411
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Хорошая работа! Пока проверяла ее, поняла, где сама накосячила в своем коде!
Я рада, что получилось в целом разобраться, единственное, я бы посоветовала еще раз вспомнить метод super() и посмотреть на "избыточность кода" с методом is_valid_alphabet.
Здорово, что удалось везде прописать типы данных и докстринги, в разы облегчило проверку!
| def __init__(self, input_path: str, output_filename: str, gc_bounds: Union[int, Tuple[int, int]] = (0, 100), | ||
| length_bounds: Union[int, Tuple[int, int]] = (0, 2**32), quality_threshold: int = 0): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Классно, что по PEP8!
| from Bio import SeqIO | ||
| from Bio.SeqUtils import gc_fraction | ||
| from Bio.SeqRecord import SeqRecord | ||
| from typing import List, Dict, Union, Tuple | ||
| from abc import ABC, abstractmethod | ||
| import os |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Секунда духоты. По PEP8 правильнее так, но это не критично совсем :)
| from Bio import SeqIO | |
| from Bio.SeqUtils import gc_fraction | |
| from Bio.SeqRecord import SeqRecord | |
| from typing import List, Dict, Union, Tuple | |
| from abc import ABC, abstractmethod | |
| import os | |
| from typing import List, Dict, Union, Tuple | |
| from abc import ABC, abstractmethod | |
| from Bio import SeqIO | |
| from Bio.SeqUtils import gc_fraction | |
| from Bio.SeqRecord import SeqRecord |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Совершенно верно, ревью это то место где душнить можно и нужно
Я бы еще пустую строку поставил между встроенными и сторонними либами
| self.gc_bounds = gc_bounds | ||
| self.length_bounds = length_bounds | ||
| self.quality_threshold = quality_threshold | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Было бы здорово прописать проверку вводного файла:
| if not os.path.isfile(self.input_path): | |
| raise FileNotFoundError(f"Input file '{self.input_path}' not found.") |
| records = list(SeqIO.parse(self.input_path, "fastq")) | ||
| return records | ||
|
|
||
| def apply_filters(self, records: List[SeqRecord]) -> List[SeqRecord]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Здорово, что прописаны типы данных везде!
| if not self.is_valid_alphabet(): | ||
| raise ValueError("Invalid alphabet for {}: {}".format(self.__class__.__name__, str(self))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Хорошо, что вызывается ошибка, в случае если подается несуществующая последовательность
| Returns: | ||
| - float: GC content of the sequence. | ||
| """ | ||
| self.validate_alphabet() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Аналогично предыдущему комментарию
| self.validate_alphabet() |
| Returns: | ||
| - RNASequence: Transcribed RNA sequence. | ||
| """ | ||
| self.validate_alphabet() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Аналогично предыдущему комментарию
| self.validate_alphabet() |
| Returns: | ||
| - List[Dict[str, int]]: List of dictionaries containing Brutto formula values for each amino acid. | ||
| """ | ||
| self.validate_alphabet() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Так как мы мы делаем валидацию при инициализации, нам это не нужно
| self.validate_alphabet() |
| Returns: | ||
| - bool: True if the alphabet is valid, False otherwise. | ||
| """ | ||
| return set(self.sequence) <= {'A', 'R', 'N', 'D', 'V', 'H', 'G', 'Q', 'E', 'I', 'L', 'K', 'M', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Так как эти буквы уже прописаны в словаре amino_brutto, можно сделать немного хитрее:
| return set(self.sequence) <= {'A', 'R', 'N', 'D', 'V', 'H', 'G', 'Q', 'E', 'I', 'L', 'K', 'M', | |
| return set(self) <= set(self.amino_brutto.keys()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
При проверке на ключи можно даже не ставить keys(), так как словарь по умолчанию итерируется по ключам. Хотя не знаю как лучше
| amino_brutto = { | ||
| "A": (3, 7, 1, 2, 0), | ||
| "R": (6, 14, 4, 2, 0), | ||
| "N": (4, 8, 2, 3, 0), | ||
| "D": (4, 7, 1, 4, 0), | ||
| "V": (5, 11, 1, 2, 0), | ||
| "H": (6, 9, 3, 2, 0), | ||
| "G": (2, 5, 1, 2, 0), | ||
| "Q": (5, 10, 2, 3, 0), | ||
| "E": (5, 9, 1, 4, 0), | ||
| "I": (6, 13, 1, 2, 0), | ||
| "L": (6, 13, 1, 2, 0), | ||
| "K": (6, 14, 2, 2, 0), | ||
| "M": (5, 11, 1, 2, 1), | ||
| "P": (5, 9, 1, 2, 0), | ||
| "S": (3, 7, 1, 3, 0), | ||
| "Y": (9, 11, 1, 3, 0), | ||
| "T": (4, 9, 11, 1, 3, 0), | ||
| "W": (11, 12, 2, 2, 0), | ||
| "F": (9, 11, 1, 2, 0), | ||
| "C": (3, 7, 1, 2, 1), | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Я бы этот словарь добавила в init_ и так как мы вносим в него изменения, обязательно добавляем метод super()
| amino_brutto = { | |
| "A": (3, 7, 1, 2, 0), | |
| "R": (6, 14, 4, 2, 0), | |
| "N": (4, 8, 2, 3, 0), | |
| "D": (4, 7, 1, 4, 0), | |
| "V": (5, 11, 1, 2, 0), | |
| "H": (6, 9, 3, 2, 0), | |
| "G": (2, 5, 1, 2, 0), | |
| "Q": (5, 10, 2, 3, 0), | |
| "E": (5, 9, 1, 4, 0), | |
| "I": (6, 13, 1, 2, 0), | |
| "L": (6, 13, 1, 2, 0), | |
| "K": (6, 14, 2, 2, 0), | |
| "M": (5, 11, 1, 2, 1), | |
| "P": (5, 9, 1, 2, 0), | |
| "S": (3, 7, 1, 3, 0), | |
| "Y": (9, 11, 1, 3, 0), | |
| "T": (4, 9, 11, 1, 3, 0), | |
| "W": (11, 12, 2, 2, 0), | |
| "F": (9, 11, 1, 2, 0), | |
| "C": (3, 7, 1, 2, 1), | |
| } | |
| def __init__(self, sequence: str): | |
| """ | |
| Initialize an AminoAcidSequence instance. | |
| """ | |
| super().__init__(sequence) | |
| amino_brutto = { | |
| "A": (3, 7, 1, 2, 0), | |
| "R": (6, 14, 4, 2, 0), | |
| "N": (4, 8, 2, 3, 0), | |
| "D": (4, 7, 1, 4, 0), | |
| "V": (5, 11, 1, 2, 0), | |
| "H": (6, 9, 3, 2, 0), | |
| "G": (2, 5, 1, 2, 0), | |
| "Q": (5, 10, 2, 3, 0), | |
| "E": (5, 9, 1, 4, 0), | |
| "I": (6, 13, 1, 2, 0), | |
| "L": (6, 13, 1, 2, 0), | |
| "K": (6, 14, 2, 2, 0), | |
| "M": (5, 11, 1, 2, 1), | |
| "P": (5, 9, 1, 2, 0), | |
| "S": (3, 7, 1, 3, 0), | |
| "Y": (9, 11, 1, 3, 0), | |
| "T": (4, 9, 11, 1, 3, 0), | |
| "W": (11, 12, 2, 2, 0), | |
| "F": (9, 11, 1, 2, 0), | |
| "C": (3, 7, 1, 2, 1), | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Я бы этот словарь добавила в init_ и так как мы вносим в него изменения, обязательно добавляем метод super()
Обрати внимание, тут маркдаун немного закофликтовал. Такие вещи надо сперва оформлять как код, а потом уже андерскоры
JuliGen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Хорошая работа! Единственное, стоит обратить внимание на повторение функций is_valid_alphabet и validate_alphabet, а также на избыточное число проверок алфавита.
| def __init__(self, input_path: str, output_filename: str, gc_bounds: Union[int, Tuple[int, int]] = (0, 100), | ||
| length_bounds: Union[int, Tuple[int, int]] = (0, 2**32), quality_threshold: int = 0): | ||
| """ | ||
| Initialize FastQFilter instance. | ||
|
|
||
| Parameters: | ||
| - input_path (str): Path to the input FastQ file. | ||
| - output_filename (str): Name for the output FastQ file. | ||
| - gc_bounds (Union[int, Tuple[int, int]]): GC content bounds for filtering. | ||
| - length_bounds (Union[int, Tuple[int, int]]): Length bounds for filtering. | ||
| - quality_threshold (int): Quality threshold for filtering. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Круто, что есть аннотация типов и докстринга
| def read_fastq(self) -> List[SeqRecord]: | ||
| """ | ||
| Read FastQ file and return a list of SeqRecord objects. | ||
| """ | ||
| records = list(SeqIO.parse(self.input_path, "fastq")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
На мой взгляд, было бы лучше читать FASTQ файл по одному сиквенсу, сразу проверять и записывать последовательность в файл, если она прошла проверку. Чтобы не хранить в памяти сразу все последовательности.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Совершенно верно
| os.makedirs(output_dir, exist_ok=True) | ||
|
|
||
| SeqIO.write(records, output_path, "fastq") | ||
| print(f"Filtered sequences written to {output_path}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Очень удобно, что для результатов создается отдельная папка!
| def __len__(self) -> int: | ||
| """ | ||
| Return the length of the sequence. | ||
|
|
||
| Returns: | ||
| - int: Length of the sequence. | ||
| """ | ||
| return len(self.sequence) | ||
|
|
||
| def __getitem__(self, index: int) -> str: | ||
| """ | ||
| Get the character at the specified index in the sequence. | ||
|
|
||
| Parameters: | ||
| - index (int): The index to retrieve. | ||
|
|
||
| Returns: | ||
| - str: The character at the specified index. | ||
| """ | ||
| return self.sequence[index] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Поскольку это абстрактный класс, думаю, что не стоит прописывать здесь какой-то смысловой код.
| Returns: | ||
| - RNASequence: Transcribed RNA sequence. | ||
| """ | ||
| self.validate_alphabet() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
В 3 предыдущих функция также происходит проверка на валидность алфавита. Наверное будет лучше 1 раз проверить алфавит при инициализации экземпляра, чтобы потом уже не проверять.
| self.validate_alphabet() |
| return 'A' | ||
| elif base == 'C': | ||
| return 'G' | ||
| elif base == 'G': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Если реализовать это через словарь - будет более красиво
| def is_valid_alphabet(self) -> bool: | ||
| """ | ||
| Check if the DNA sequence contains a valid alphabet. | ||
|
|
||
| Returns: | ||
| - bool: True if the alphabet is valid, False otherwise. | ||
| """ | ||
| return set(self.sequence) <= {'A', 'T', 'C', 'G'} | ||
|
|
||
| class RNASequence(NucleicAcidSequence): | ||
| """ | ||
| Class representing RNA sequences. | ||
| """ | ||
| def is_valid_alphabet(self) -> bool: | ||
| """ | ||
| Check if the RNA sequence contains a valid alphabet. | ||
|
|
||
| Returns: | ||
| - bool: True if the alphabet is valid, False otherwise. | ||
| """ | ||
| return set(self.sequence) <= {'A', 'U', 'C', 'G'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Для того, чтобы не прописывать в каждом классе, который наследуется от NucleicAcidSequence метод is_valid_alphabet можно сделать классовый атрибут alphabet для RNASequence и для DNASequence. И метод для проверки прописать в NucleicAcidSequence:
def is_valid_alphabet(self, seq: str) -> bool:
"""Checks the sequence to match the alphabet"""
return set(seq).issubset(type(self).alphabet)
В данном случае в качестве alphabet возьмется алфавит в зависимости от класса.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
У маркдауновского кода добавляйте python в первой строчке после ``` - так будет подсвечиваться синтакс:)
iliapopov17
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Что ж мне всё очень понравилось! Всё довольно хорошо, вразумительно и толково. Мой код наверняка хуже, но я на него пока не хочу смотреть. Единственные два момента, которые, действительно, нуждаются в улучшении:
- Комплементарность через словарь.
- Валидация 3 раза подряд не нужна
Всё остальное это просто пожелания, чтобы из конфетки Ротфронт сделать конфетку Крокант.
| def brutto_count(self) -> List[Dict[str, int]]: | ||
| """ | ||
| Calculate the Brutto formula values for each amino acid in the sequence. | ||
|
|
||
| Returns: | ||
| - List[Dict[str, int]]: List of dictionaries containing Brutto formula values for each amino acid. | ||
| """ | ||
| self.validate_alphabet() | ||
| elements = ["C", "H", "N", "O", "S"] | ||
| result = [] | ||
| for letter in self.sequence: | ||
| brutto_values = self.amino_brutto.get(letter, (0, 0, 0, 0, 0)) | ||
| result.append(dict(zip(elements, brutto_values))) | ||
| return result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Вместо использования цикла for и append, можно использовать генератор списков для более компактного кода
| def brutto_count(self) -> List[Dict[str, int]]: | |
| """ | |
| Calculate the Brutto formula values for each amino acid in the sequence. | |
| Returns: | |
| - List[Dict[str, int]]: List of dictionaries containing Brutto formula values for each amino acid. | |
| """ | |
| self.validate_alphabet() | |
| elements = ["C", "H", "N", "O", "S"] | |
| result = [] | |
| for letter in self.sequence: | |
| brutto_values = self.amino_brutto.get(letter, (0, 0, 0, 0, 0)) | |
| result.append(dict(zip(elements, brutto_values))) | |
| return result | |
| def brutto_count(self) -> List[Dict[str, int]]: | |
| """ | |
| Calculate the Brutto formula values for each amino acid in the sequence. | |
| Returns: | |
| - List[Dict[str, int]]: List of dictionaries containing Brutto formula values for each amino acid. | |
| """ | |
| self.validate_alphabet() | |
| elements = ["C", "H", "N", "O", "S"] | |
| return [dict(zip(elements, self.amino_brutto.get(letter, (0, 0, 0, 0, 0)))) for letter in self.sequence] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Хорошее предложение! Хотя в таком случае надо следить за длиной строки
| if gc_pass and length_pass and quality_pass: | ||
| filtered_records.append(record) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Вместо if ___ and ___ and ___: можно всё запихнуть в if all:
Хотя это очень сильно дискутабельно, поскольку то, что предложил я выглядит монструозно и не элегантно. Просто весь остальной код вполне хорошо, и я пока не знаю, что можно отревьюить...
| if gc_pass and length_pass and quality_pass: | |
| filtered_records.append(record) | |
| if all([ | |
| self.check_bounds(gc_content, self.gc_bounds), | |
| self.check_bounds(length, self.length_bounds), | |
| avg_quality >= self.quality_threshold | |
| ]): | |
| filtered_records.append(record) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Отличное предложение!
| self.validate_alphabet() | ||
| gc_count = sum(1 for base in self.sequence if base in {'G', 'C'}) | ||
| return gc_count / len(self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Мне кажется, такая запись будет более понятна
| self.validate_alphabet() | |
| gc_count = sum(1 for base in self.sequence if base in {'G', 'C'}) | |
| return gc_count / len(self) | |
| self.validate_alphabet() | |
| gc_count = self.sequence.count('G') + self.sequence.count('C') | |
| return gc_count / len(self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Но так получается подсчет за 2 прогона. В общем получается:
- Вариант немного эффективнее
- Вариант немного понятнее
Какой лучше я тут сказать не могу, ахах, на ваш вкус)
| def read_fastq(self) -> List[SeqRecord]: | ||
| """ | ||
| Read FastQ file and return a list of SeqRecord objects. | ||
| """ | ||
| records = list(SeqIO.parse(self.input_path, "fastq")) | ||
| return records |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это уже из разряда "что можно сделать, чтобы вообще конфетка была". Можно добавить блок исключений при чтении fastq файла для более корректной обработки возможных ошибок ввода-вывода
| def read_fastq(self) -> List[SeqRecord]: | |
| """ | |
| Read FastQ file and return a list of SeqRecord objects. | |
| """ | |
| records = list(SeqIO.parse(self.input_path, "fastq")) | |
| return records | |
| def read_fastq(self) -> List[SeqRecord]: | |
| """ | |
| Read FastQ file and return a list of SeqRecord objects. | |
| """ | |
| try: | |
| records = list(SeqIO.parse(self.input_path, "fastq")) | |
| return records | |
| except IOError as e: | |
| print(f"Error reading FastQ file: {e}") | |
| return [] |
| if base == 'A': | ||
| return 'T' | ||
| elif base == 'T': | ||
| return 'A' | ||
| elif base == 'C': | ||
| return 'G' | ||
| elif base == 'G': | ||
| return 'C' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Да, нам говорили скрывать комментарии других ревьюеров при составлении своего ревью! Но, признаюсь честно, это бросается в глаза... Лучше сделать через словарь.
Хотя у меня в коде наверное всё также и реализовано...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
А что с другим ревьюером? Там вроде тоже советовали убрать это в словарь и совершенно верно, правильно что вы это написали
| SeqIO.write(records, output_path, "fastq") | ||
| print(f"Filtered sequences written to {output_path}") | ||
|
|
||
| class BiologicalSequence(ABC): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Для абстрактного класса он слишком не абстрактный как-то... В нём много функционала в общем!
| def apply_filters(self, records: List[SeqRecord]) -> List[SeqRecord]: | ||
| """ | ||
| Filter SeqRecord objects based on specified criteria. | ||
|
|
||
| Parameters: | ||
| - records (List[SeqRecord]): List of SeqRecord objects to filter. | ||
|
|
||
| Returns: | ||
| - List[SeqRecord]: Filtered list of SeqRecord objects. | ||
| """ | ||
| filtered_records = [] | ||
|
|
||
| for record in records: | ||
| gc_content = gc_fraction(record.seq) | ||
| length = len(record.seq) | ||
| avg_quality = sum(record.letter_annotations["phred_quality"]) / len(record.letter_annotations["phred_quality"]) | ||
|
|
||
| gc_pass = self.check_bounds(gc_content, self.gc_bounds) | ||
| length_pass = self.check_bounds(length, self.length_bounds) | ||
| quality_pass = avg_quality >= self.quality_threshold | ||
|
|
||
| if gc_pass and length_pass and quality_pass: | ||
| filtered_records.append(record) | ||
|
|
||
| return filtered_records |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Вместо вызова self.check_bounds для каждого критерия, можно вычислить результаты сразу:
| def apply_filters(self, records: List[SeqRecord]) -> List[SeqRecord]: | |
| """ | |
| Filter SeqRecord objects based on specified criteria. | |
| Parameters: | |
| - records (List[SeqRecord]): List of SeqRecord objects to filter. | |
| Returns: | |
| - List[SeqRecord]: Filtered list of SeqRecord objects. | |
| """ | |
| filtered_records = [] | |
| for record in records: | |
| gc_content = gc_fraction(record.seq) | |
| length = len(record.seq) | |
| avg_quality = sum(record.letter_annotations["phred_quality"]) / len(record.letter_annotations["phred_quality"]) | |
| gc_pass = self.check_bounds(gc_content, self.gc_bounds) | |
| length_pass = self.check_bounds(length, self.length_bounds) | |
| quality_pass = avg_quality >= self.quality_threshold | |
| if gc_pass and length_pass and quality_pass: | |
| filtered_records.append(record) | |
| return filtered_records | |
| def apply_filters(self, records: List[SeqRecord]) -> List[SeqRecord]: | |
| """ | |
| Filter SeqRecord objects based on specified criteria. | |
| Parameters: | |
| - records (List[SeqRecord]): List of SeqRecord objects to filter. | |
| Returns: | |
| - List[SeqRecord]: Filtered list of SeqRecord objects. | |
| """ | |
| return [record for record in records if | |
| self.check_bounds(gc_fraction(record.seq), self.gc_bounds) | |
| and self.check_bounds(len(record.seq), self.length_bounds) | |
| and sum(record.letter_annotations["phred_quality"]) / len(record.letter_annotations["phred_quality"]) >= self.quality_threshold] |
| Returns: | ||
| - RNASequence: Transcribed RNA sequence. | ||
| """ | ||
| self.validate_alphabet() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Присоединяюсь к другим ревьюерам.
Review ELMOD1