Seq
Seq
Core Principle: The
Bio.Seq.Seqclass provides an immutable sequence container equipped with domain-specific biological transformations, including reverse complementation, transcription, and translation.
Creating and using
Seq('ATGGCCATTGTAATGGGCCGC')
Seq supports nearly the whole str interface, so most string intuition transfers directly.
Bio.Alphabet is gone
Removed in Biopython 1.78. This is a TypeError now:
from Bio.Alphabet import IUPAC # ImportError
Seq("ATG", IUPAC.unambiguous_dna) # TypeError
A Seq is just letters. The library does not know whether ATG is DNA or protein, and mostly does not need to. Where a format requires it (writing GenBank), you supply it as metadata:
record.annotations["molecule_type"] = "DNA"
Every tutorial written before 2020 will show the alphabet API. See Biopython Deprecations.
The biology methods
Seq('GCGGCCCAUUACAAUGGCCAU')
complement() vs reverse_complement(): complement alone gives you the opposite strand read in the same direction, which is almost never biologically meaningful. For "the other strand's sequence as it would be read", you want reverse_complement().
Translation, carefully
Traceback (most recent call last):
File "/Users/leviosman/Developer/python-for-bio/webapp/scripts/curriculum/generate_cell_outputs.py", line 126, in run_cell_capture
exec(compile(head, "<cell>", "exec"), namespace)
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<cell>", line 4, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/site-packages/Bio/Seq.py", line 1617, in translate
_translate_str(str(self), table, stop_symbol, to_stop, cds, gap=gap)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/site-packages/Bio/Seq.py", line 2866, in _translate_str
raise CodonTable.TranslationError(
f"Final codon '{sequence[-3:]}' is not a stop codon"
)
Bio.Data.CodonTable.TranslationError: Final codon 'CGC' is not a stop codon
Points to internalise:
to_stop=Trueis usually what you mean. Without it, translation runs to the end of the sequence and you getMAIVMGR*KGAR*with internal stops.cds=Truevalidates: it requires a length divisible by 3, a valid start codon, a single terminal stop, and no internal stops. It raisesTranslationErrorotherwise. Use it when you believe you have a real CDS: the exception is information.- Partial codons warn (
BiopythonWarning: Partial codon) and are dropped. - Table matters. Mitochondrial genomes use different codon tables;
TGAis a stop in the standard code and tryptophan in vertebrate mitochondria. Getting this wrong silently truncates your protein.
Table 2 Vertebrate Mitochondrial, SGC1 | T | C | A | G | --+---------+---------+---------+---------+-- T | TTT F | TCT S | TAT Y | TGT C | T T | TTC F | TCC S | TAC Y | TGC C | C T | TTA L | TCA S | TAA Stop| TGA W | A T | TTG L | TCG S | TAG Stop| TGG W | G --+---------+---------+---------+---------+-- C | CTT L | CCT P | CAT H | CGT R | T C | CTC L | CCC P | CAC H | CGC R | C C | CTA L | CCA P | CAA Q | CGA R | A C | CTG L | CCG P | CAG Q | CGG R | G --+---------+---------+---------+---------+-- A | ATT I(s)| ACT T | AAT N | AGT S | T A | ATC I(s)| ACC T | AAC N | AGC S | C A | ATA M(s)| ACA T | AAA K | AGA Stop| A A | ATG M(s)| ACG T | AAG K | AGG Stop| G --+---------+---------+---------+---------+-- G | GTT V | GCT A | GAT D | GGT G | T G | GTC V | GCC A | GAC D | GGC G | C G | GTA V | GCA A | GAA E | GGA G | A G | GTG V(s)| GCG A | GAG E | GGG G | G --+---------+---------+---------+---------+--
Immutability
Traceback (most recent call last):
File "/Users/leviosman/Developer/python-for-bio/webapp/scripts/curriculum/generate_cell_outputs.py", line 132, in run_cell_capture
exec(compile(code, "<cell>", "exec"), namespace)
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<cell>", line 1, in <module>
TypeError: 'Seq' object does not support item assignment
Immutability means Seq is hashable and safe to share. Use MutableSeq for in-place editing, then convert back.
For simulating a substitution the string route is usually clearer:
mutated = Seq(str(s)[:pos] + alt + str(s)[pos + len(ref):])
Undefined and partial sequences
Traceback (most recent call last):
File "/Users/leviosman/Developer/python-for-bio/webapp/scripts/curriculum/generate_cell_outputs.py", line 128, in run_cell_capture
value = eval(compile(tail, "<cell>", "eval"), namespace)
File "<cell>", line 4, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/site-packages/Bio/Seq.py", line 407, in __str__
return self._data.decode("ASCII")
~~~~~~~~~~~~~~~~~^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/site-packages/Bio/Seq.py", line 155, in decode
return bytes(self).decode(encoding)
~~~~~^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/site-packages/Bio/Seq.py", line 2387, in __bytes__
raise UndefinedSequenceError("Sequence content is undefined")
Bio.Seq.UndefinedSequenceError: Sequence content is undefined
This is how Biopython represents a record whose length is known from a header but whose sequence was not loaded (common with GenBank contig records and some SeqIO indexing modes). Guard with a try/except if you parse arbitrary files.
Bioinformatics examples
from Bio.Seq import Seq
from Bio.SeqUtils import gc_fraction, molecular_weight
s = Seq("ATGGCCATTGTAATGGGCCGCTGAAAGGGTGCCCGATAG")
gc_fraction(s) # 0.0–1.0 (NOT the removed GC(), 0–100)
molecular_weight(s, seq_type="DNA")
s.translate(to_stop=True) # MAIVMGR
# find ORFs in all six frames
def orfs(seq, min_aa=50):
for strand, nuc in [(+1, seq), (-1, seq.reverse_complement())]:
for frame in range(3):
trimmed = nuc[frame: len(nuc) - (len(nuc) - frame) % 3]
for prot in trimmed.translate().split("*"):
if len(prot) >= min_aa:
yield strand, frame, prot
# reverse-strand variant: get the sequence as the transcript reads it
if strand == "-":
coding = genomic_seq.reverse_complement()
# codon usage
codons = [str(s[i:i+3]) for i in range(0, len(s) - 2, 3)]
pd.Series(codons).value_counts()
# GC at the third codon position (GC3): a codon-bias measure
gc3 = sum(1 for i in range(2, len(s), 3) if s[i] in "GC") / (len(s) // 3)
Ambiguity codes
IUPAC ambiguity letters (N, R, Y, W, S, K, M, ...) are handled correctly by complement/reverse_complement: R (A or G) complements to Y (C or T). Translation of an ambiguous codon gives X where the amino acid is undetermined, or the specific residue where all possibilities agree.
Real-world sequences contain N (unknown base) and soft-masked lowercase (repeat-masked regions). s.upper() before comparisons, and decide deliberately whether N counts in your GC denominator: gc_fraction has an ambiguous= parameter for this.
Common mistakes
- Using
Bio.Alphabet. Removed. ungap(),tomutable(),toseq(). Removed. Use.replace("-", "")and the constructors.translate()withoutto_stop=True, getting internal stops.- Wrong codon table for mitochondrial or bacterial sequences.
complement()where you meantreverse_complement().- Forgetting reverse-strand genes need reverse-complementing before translation.
- Assuming
Seqis mutable. - Ignoring soft-masking. Lowercase bases fail a naive
== "G"test. SeqUtils.GC(): removed in 1.82, and it returned a percentage whilegc_fractionreturns a fraction. Off by 100×.
See also
SeqRecord · SeqIO · Bio.SeqUtils · Biopython Deprecations · Applied - Sequence Composition Analysis · Applied - FASTA and FASTQ Workflows
Exercise
Biopython is not installed here, so build the standard
codon table yourself and translate with it. The table is generated from the
base order TCAG: iterate the first, second and third position in that order
through all 64 combinations, and read off the amino acids in the same order
from
FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG
Write translate_report(raw) returning three strings. Upper-case the input
first, because soft-masked lowercase bases are common and a naive lookup would
miss every one of them. Translate frame 0 only, dropping a trailing partial
codon rather than padding it, and rendering any codon containing a character
other than A, C, G or T as X. Return the full translation with *
for stop codons, then the translation truncated before the first stop, then the
same truncated translation of the reverse complement. Print them as full=,
to_stop= and rc_to_stop=. Complement A to T, C to G and their
inverses, and map anything else to N. A sequence shorter than three bases
yields three empty values, and the three lines must still be printed.
Test cases · 4
| # | via | input | expected stdout |
|---|---|---|---|
| 1 | stdin | ATGGCCATTGTAATGGGCCGCTGAAAGGGTGCCCGATAG | full=MAIVMGR*KGAR* to_stop=MAIVMGR rc_to_stop=LSGTLSAAHYNGH |
| 2 | stdin | atggaggagccgcagtcagatcctagcgtcgagccccctctgagtcaggaaacattttca | full=MEEPQSDPSVEPPLSQETFS to_stop=MEEPQSDPSVEPPLSQETFS rc_to_stop= |
| 3 | stdin | ATGNNNCATTAAGGGCC | full=MXH*G to_stop=MXH rc_to_stop=GP |
| 4 | stdin | AT | full= to_stop= rc_to_stop= |
Hints · 3
01Hint
Three nested loops over "TCAG" and an index into the amino-acid string build all 64 entries.
02Hint
table.get(codon, "X") handles ambiguous codons without a separate test.
03Hint
Truncating at the first stop is protein.split("*")[0].