| name | python-bio-strings |
| description | DNA/RNA as Python strings: reverse complement (maketrans/translate), GC content, codon split, motif find (find/re.finditer), FASTA header parsing. Use for reverse complement, transcription, motif counting without Biopython. |
| tool_type | python |
| primary_tool | Python |
Python Strings for Bioinformatics
When to Use
- Writing a quick reverse-complement, transcription, or codon-splitting function without pulling in Biopython.
- Computing GC content, nucleotide composition, or motif/codon counts from raw sequence strings.
- Parsing FASTA/UniProt headers (
>sp|P04637|P53_HUMAN ...) into accession/description fields.
- Finding all (possibly overlapping) occurrences of a motif, or IUPAC-ambiguous patterns via regex.
- Debugging why a slice or
.count() gave the wrong answer against 1-based genomic coordinates (GFF/VCF).
Version Compatibility
Pure standard library — Python ≥3.8 (f-strings with =, str.maketrans/translate are stable since Python 3). No external packages required. For anything beyond ad-hoc string ops (parsing real FASTA files, alphabet validation, translation tables), switch to Bio.Seq/Bio.SeqIO from Biopython ≥1.78 — see python-bio-sequences.
Prerequisites
- Comfort with Python string slicing (
seq[start:stop:step]) and list comprehensions.
- No packages to install — this is stdlib only (
re for regex patterns).
Core Patterns
Goal: compute a reverse complement, transcribe DNA→RNA, and split into codons correctly.
Approach: use str.maketrans + translate() for complementing (never chained replace() — see Pitfalls), then reverse with [::-1].
def reverse_complement(dna: str) -> str:
"""Return the reverse complement of a DNA string (uppercase A/T/G/C only)."""
complement_table = str.maketrans('ATGC', 'TACG')
return dna.upper().translate(complement_table)[::-1]
def transcribe(dna_coding_strand: str) -> str:
"""Transcribe a DNA coding strand to mRNA (T -> U)."""
return dna_coding_strand.upper().replace('T', 'U')
def gc_content(seq: str) -> float:
"""Fraction of G/C bases in seq, case-insensitive."""
seq = seq.upper()
return (seq.count('G') + seq.count('C')) / len(seq)
def codons(mrna: str) -> list[str]:
"""Split an mRNA/coding sequence into non-overlapping codons."""
return [mrna[i:i + 3] for i in range(0, len(mrna), 3)]
dna = "ATGCGATCGATCGTAG"
assert reverse_complement(dna) == "CTACGATCGATCGCAT"
assert transcribe(dna) ==
(gc_content(dna), ) ==
codons() == [, , , ]
Goal: find all motif occurrences (including overlaps) and IUPAC-ambiguous patterns.
Approach: str.find() in a loop for overlapping literal matches; re.finditer for ambiguous/IUPAC motifs (e.g. TATA box TATA[AT]A[AT]).
import re
def find_all(seq: str, motif: str) -> list[int]:
"""Return all start positions of motif in seq, including overlapping matches."""
positions = []
pos = seq.find(motif)
while pos != -1:
positions.append(pos)
pos = seq.find(motif, pos + 1)
return positions
def find_tata_boxes(seq: str) -> list[tuple[int, str]]:
"""Find TATA-box-like elements (TATA[AT]A[AT]) and their start positions."""
return [(m.start(), m.group()) for m in re.finditer(r'TATA[AT]A[AT]', seq)]
assert find_all("AAAA", "AA") == [0, 1, 2]
assert "AAAA".count("AA") == 2
assert find_tata_boxes("NNTATAAAANN") == [(2, 'TATAAAA')]
Goal: parse a UniProt/NCBI-style FASTA header into its fields.
Approach: split on | for UniProt (db|accession|entry_name description), or on whitespace for simple >id description headers.
def parse_uniprot_header(header: str) -> dict:
"""Parse a '>sp|ACCESSION|ENTRY_NAME description' UniProt FASTA header."""
body = header.lstrip('>')
db, accession, rest = body.split('|', 2)
entry_name, _, description = rest.partition(' ')
return {"db": db, "accession": accession, "entry_name": entry_name, "description": description}
header = ">sp|P04637|P53_HUMAN Cellular tumor antigen p53 OS=Homo sapiens"
parsed = parse_uniprot_header(header)
assert parsed["accession"] == "P04637"
assert parsed["entry_name"] == "P53_HUMAN"
Sequence Features
seq = "ATGGCCGATTAGCCA"
seq.startswith('ATG')
seq[-3:] in ('TAA', 'TAG', 'TGA')
'GATTAG' in seq
all(c in 'ATGC' for c in seq.upper())
Pitfalls
- Chained
replace() fails for complement: dna.replace('A','T').replace('T','A') converts all A→T then all T→A, including the ones just created — every base ends up A. Use str.maketrans + translate().
find() returns -1, not None: if pos: is truthy for -1 (and falsy only for 0, a valid match at the start). Always check if pos != -1:.
count() is non-overlapping: "AAAA".count("AA") = 2, not 3. Use the find()-loop pattern above for overlapping matches.
- Case sensitivity:
.count('G') misses lowercase 'g' (common in soft-masked repeat sequences). Always .upper() first.
- Strings are immutable:
seq[0] = 'C' raises TypeError. Build new strings with slicing/concatenation, str.translate, or ''.join(...).
- Bioinformatics coordinates are 1-based, inclusive: GFF/VCF/SAM position 100 is Python index 99; a 1-based inclusive range
[start, end] becomes the Python slice seq[start-1:end].
See Also
python-bio-sequences — Biopython Seq/SeqRecord objects for real FASTA/GenBank I/O and translation tables.
python-bio-regular-expressions — regex patterns for IUPAC ambiguity codes, restriction sites, and complex motifs.
python-bio-lists — list/slice mechanics that underlie codon splitting and k-mer windows.
bio-core-motif-discovery — motif discovery and enrichment beyond literal/regex matching.