Exercise 006¶
In [2]:
Copied!
# Please execute this cell to download the necessary data
!wget https://raw.githubusercontent.com/JR-1991/PythonProgramming2026/master/data/all_sequences.fasta
# Please execute this cell to download the necessary data
!wget https://raw.githubusercontent.com/JR-1991/PythonProgramming2026/master/data/all_sequences.fasta
--2026-06-16 14:15:52-- https://raw.githubusercontent.com/JR-1991/PythonProgramming2026/master/data/all_sequences.fasta Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ... Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 2940315 (2.8M) [text/plain] Saving to: ‘all_sequences.fasta’ all_sequences.fasta 0%[ ] 0 --.-KB/s all_sequences.fasta 100%[===================>] 2.80M --.-KB/s in 0.07s 2026-06-16 14:15:52 (42.7 MB/s) - ‘all_sequences.fasta’ saved [2940315/2940315]
DNASequence class¶
Read the FASTA file all_sequences.fasta and store header info and sequence in a suitable class. Make sure that at the initialization of the object, the following atrributes are present:
idorganismsequencegc_contentlength
Tips
- Your
__init__-method arguments do not have to contain all expected attributes if you can derive them from another attribute. The__init__-method is a function and you can execute any code you want upon initialization. Make sure to assign your calculation to the appropriate attribute viaself.xyz.- Dataclasses are a convinient way to create classes that simply hold data. You can make use of them to simplify the process due to the automatic generation of a
__init__-method. But keep in mind that this excludes additional calculation you would have otherwise put into your custom__init__-method.
In [1]:
Copied!
class DNASequence: # Pascal-case naming convention: e.g. ProteinSequence
def __init__(
self, # Refers to the object to create
sequence: str,
organism: str,
id: str,
):
# "External" attributes
self.sequence = sequence.upper() # Turns all characters into upper-case
self.organism = organism
self.id = id
# Calculated attributes
self.length = len(self.sequence)
self.gc_content = (
self.sequence.count("G") + self.sequence.count("C")
) / self.length
class DNASequence: # Pascal-case naming convention: e.g. ProteinSequence
def __init__(
self, # Refers to the object to create
sequence: str,
organism: str,
id: str,
):
# "External" attributes
self.sequence = sequence.upper() # Turns all characters into upper-case
self.organism = organism
self.id = id
# Calculated attributes
self.length = len(self.sequence)
self.gc_content = (
self.sequence.count("G") + self.sequence.count("C")
) / self.length
In [5]:
Copied!
DNASequence(
sequence="ATG",
organism="ecoli",
id="someID",
)
DNASequence(
sequence="ATG",
organism="ecoli",
id="someID",
)
Out[5]:
<__main__.DNASequence at 0x7e6bcb202030>
In [6]:
Copied!
# Using dataclasses
from dataclasses import dataclass
@dataclass
class DNASequenceDC:
sequence: str
organism: str
id: str
length: int
gc_content: float
instance = DNASequenceDC(
sequence="ATG",
organism="ecoli",
id="someID",
length=3,
gc_content=0.5,
)
print(instance)
# Using dataclasses
from dataclasses import dataclass
@dataclass
class DNASequenceDC:
sequence: str
organism: str
id: str
length: int
gc_content: float
instance = DNASequenceDC(
sequence="ATG",
organism="ecoli",
id="someID",
length=3,
gc_content=0.5,
)
print(instance)
DNASequenceDC(sequence='ATG', organism='ecoli', id='someID', length=3, gc_content=0.5)
In [7]:
Copied!
def read_fasta(path: str) -> list[DNASequence]:
"""Reads a FASTA file and parses all entries into DNASequence objects
Args:
path: Path to the FASTA file to parse
Returns:
list[DNASequence]: Parsed sequences wrapped in DNASequence objects
"""
sequences = []
data = open(path).readlines()
for i, line in enumerate(data):
if not i % 2:
# Grab the header and continue
organism, id = line.lstrip(">").split("|")
continue
obj = DNASequence(
sequence=line.strip(),
organism=organism.strip(),
id=id.strip(),
)
sequences.append(obj)
return sequences
def read_fasta(path: str) -> list[DNASequence]:
"""Reads a FASTA file and parses all entries into DNASequence objects
Args:
path: Path to the FASTA file to parse
Returns:
list[DNASequence]: Parsed sequences wrapped in DNASequence objects
"""
sequences = []
data = open(path).readlines()
for i, line in enumerate(data):
if not i % 2:
# Grab the header and continue
organism, id = line.lstrip(">").split("|")
continue
obj = DNASequence(
sequence=line.strip(),
organism=organism.strip(),
id=id.strip(),
)
sequences.append(obj)
return sequences
In [10]:
Copied!
sequences = read_fasta("./all_sequences.fasta")
sequence = sequences[0]
sequences = read_fasta("./all_sequences.fasta")
sequence = sequences[0]
In [11]:
Copied!
# Using the objects/instances
print(f"The sequence of the first sequence is: {sequence.sequence}")
# Using the objects/instances
print(f"The sequence of the first sequence is: {sequence.sequence}")
The sequence of the first sequence is: ATGCGTTCTCGCTATTTGTTACATCAATATTTTGTTCAGGTACAGTTTGCAGCGCCGTCGCCAGCGCCAACGGATTCCATGTCATATATTATTCCATATAGATTAAGTTTAAATATTAATAAAATGAATATTTGCAATACGTAATTATCTTACCAGCTATAGACAAAAAAAAACCATCCAAATCTGGATGGCTTTTCATAATTCAGAGGAACTAGCTGCGCTGACGAACCGCTTCAAATAAGCAAATTCCGGTTGCAACCGAAACGTTCAGGGAAGAAACACTTCCTGCCATTGGGATGCTGATCAACTCATCGCAATGTTCACGGGTCAGGCGACGCATACCTTCACCTTCCGCGCCCATCACCAGCGCCAGGCGTCCGGTCATTTTGCTTTGATAGAGCGTATGATCCGCCTCACCTGCCGTACCGACGATCCAGATATTCTCTTCCTGCAACATACGCATGGTGCGCGCAAGGTTAGTCACCCGAATCAGTGGAACGCTTTCTGCCGCGCCGCAGGCTACTTTTTTCGCCGTGGCGTTGAGCTGTGCGGAGCGATCTTTCGGCACAATCACCGCGTGAACGCCAGCAGCGTCCGCGCTACGCAGGCACGCGCCGAGGTTGTGCGGATCGGTTACACCGTCGAGGATCAGCAGGAACGGTTGATCGAGCGAAGCGATCAGATCCGGCAGATCGTTTTCCTGGTACTGACGTCCTGGCTTCACGCGGGCGATAATGCCCTGATGCACGGCACCGTCGCTTTTCTCGTCGAGATATTGGCGGTTTGCCAACTGGATAACCACGCCCTGGGACTCAAGGGCGTGGATCAGCGGTAACAGACGTTTATCTTCACGGCCTTTTAAAATAA
In [14]:
Copied!
# Downsides of using standard python classes
# Case: DNASequence class, but the user provides a number instead of a string for the ID
instance = DNASequence(
sequence="SomeSequence",
organism="organism",
id=10.0,
)
instance.__dict__
# Downsides of using standard python classes
# Case: DNASequence class, but the user provides a number instead of a string for the ID
instance = DNASequence(
sequence="SomeSequence",
organism="organism",
id=10.0,
)
instance.__dict__
Out[14]:
{'sequence': 'SOMESEQUENCE',
'organism': 100.0,
'id': 10.0,
'length': 12,
'gc_content': 0.08333333333333333}
In [15]:
Copied!
# Using pydantic
from pydantic import BaseModel
class DNASequencePyDantic(BaseModel):
sequence: str
organism: str
id: str
instance = DNASequencePyDantic(
sequence="AAA",
organism="organism",
id="10.0",
)
print(instance.model_dump_json(indent=4))
# Using pydantic
from pydantic import BaseModel
class DNASequencePyDantic(BaseModel):
sequence: str
organism: str
id: str
instance = DNASequencePyDantic(
sequence="AAA",
organism="organism",
id="10.0",
)
print(instance.model_dump_json(indent=4))
{
"sequence": "AAA",
"organism": "organism",
"id": "10.0"
}
In [16]:
Copied!
# We get input validation, that saves time and nerves!
DNASequencePyDantic(
sequence=100.0,
organism="organism",
id="10.0",
)
# We get input validation, that saves time and nerves!
DNASequencePyDantic(
sequence=100.0,
organism="organism",
id="10.0",
)
--------------------------------------------------------------------------- ValidationError Traceback (most recent call last) /tmp/ipykernel_1026/43964331.py in <cell line: 0>() ----> 1 DNASequencePyDantic( 2 sequence=100.0, 3 organism="organism", 4 id="10.0", 5 ) /usr/local/lib/python3.12/dist-packages/pydantic/main.py in __init__(self, **data) 248 # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks 249 __tracebackhide__ = True --> 250 validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self) 251 if self is not validated_self: 252 warnings.warn( ValidationError: 1 validation error for DNASequencePyDantic sequence Input should be a valid string [type=string_type, input_value=100.0, input_type=float] For further information visit https://errors.pydantic.dev/2.12/v/string_type
Magic Methods - Alignment by ==¶
This is an optional exercise
Can you extend the class to output the identity between the two sequences (stored as an attribute) when the == comparison operator is used? Apply the implementation to two sequences that you have chosen and use the supplied get_identity function.
Learn more about Magic methods
In [23]:
Copied!
# Execute this cell to use all packages
%pip install biopython
from Bio import pairwise2
def get_identity(seq1: str, seq2: str):
"""Aligns two sequences using BioPython
Args:
seq1 (str): Query sequence to align to
seq2 (str): Target sequence to align with
Returns:
float: Identity of the resulting alignment
"""
return pairwise2.align.globalxx(seq1, seq2, score_only=True) / len(seq1) # type: ignore
# Execute this cell to use all packages
%pip install biopython
from Bio import pairwise2
def get_identity(seq1: str, seq2: str):
"""Aligns two sequences using BioPython
Args:
seq1 (str): Query sequence to align to
seq2 (str): Target sequence to align with
Returns:
float: Identity of the resulting alignment
"""
return pairwise2.align.globalxx(seq1, seq2, score_only=True) / len(seq1) # type: ignore
Collecting biopython Downloading biopython-1.87-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (13 kB) Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (from biopython) (2.0.2) Downloading biopython-1.87-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (3.2 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/3.2 MB ? eta -:--:-- ━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.3/3.2 MB 8.4 MB/s eta 0:00:01 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╺━━━━━━━━━━ 2.4/3.2 MB 34.5 MB/s eta 0:00:01 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.2/3.2 MB 35.0 MB/s eta 0:00:00 Installing collected packages: biopython Successfully installed biopython-1.87
/usr/local/lib/python3.12/dist-packages/Bio/pairwise2.py:278: BiopythonDeprecationWarning: Bio.pairwise2 has been deprecated, and we intend to remove it in a future release of Biopython. As an alternative, please consider using Bio.Align.PairwiseAligner as a replacement, and contact the Biopython developers if you still need the Bio.pairwise2 module. warnings.warn(
In [20]:
Copied!
# This is an operator in python (alongside +, -, *, /, ...)
10.0 == 20.0
# This is an operator in python (alongside +, -, *, /, ...)
10.0 == 20.0
Out[20]:
False
In [21]:
Copied!
class DNASequence:
def __init__(
self, # Refers to the object to create
sequence: str,
organism: str,
id: str,
):
# "External" attributes
self.sequence = sequence.upper()
self.organism = organism
self.id = id
# Calculated attributes
self.length = len(sequence)
self.gc_content = (
self.sequence.count("G") + self.sequence.count("C")
) / self.length
def __eq__(self, other: DNASequence):
"""Overrides the == operator and runs this method instead.
We first check that the other object we want to align is of
the same type. If so, we will use the 'get_identity' function
to receive the percent identity of both sequences.
"""
assert isinstance(other, DNASequence), (
f"Only types of 'DNASequence' can be used for comparison. Got {type(other)}, which is invalid."
)
return get_identity(self.sequence, other.sequence)
class DNASequence:
def __init__(
self, # Refers to the object to create
sequence: str,
organism: str,
id: str,
):
# "External" attributes
self.sequence = sequence.upper()
self.organism = organism
self.id = id
# Calculated attributes
self.length = len(sequence)
self.gc_content = (
self.sequence.count("G") + self.sequence.count("C")
) / self.length
def __eq__(self, other: DNASequence):
"""Overrides the == operator and runs this method instead.
We first check that the other object we want to align is of
the same type. If so, we will use the 'get_identity' function
to receive the percent identity of both sequences.
"""
assert isinstance(other, DNASequence), (
f"Only types of 'DNASequence' can be used for comparison. Got {type(other)}, which is invalid."
)
return get_identity(self.sequence, other.sequence)
In [25]:
Copied!
dna_sequences = read_fasta("./all_sequences.fasta")
# Lets align two sequences
(dna_sequences[11] == dna_sequences[100]) * 100
dna_sequences = read_fasta("./all_sequences.fasta")
# Lets align two sequences
(dna_sequences[11] == dna_sequences[100]) * 100
Out[25]:
60.983606557377044
In [ ]:
Copied!