Variables and Data Types
When to Use
- Writing or reviewing introductory Python bioinformatics scripts that assign sequences, counts, or scores to variables.
- Explaining why
alias = original doesn't copy a list/dict, or why seq = seq + "AAA" doesn't mutate the original string.
- Choosing between
int, float, str, bool, None for genomic data (coordinates, GC content, E-values, sequences, missing annotations).
- Slicing/indexing DNA/RNA/protein strings (start/stop codons, reverse complement setup, coding regions).
- Parsing simple text formats (FASTA headers, delimited fields) with
split()/join()/strip().
Version Compatibility
Python ≥3.9 (f-strings, arbitrary-precision int — no special version dependency; syntax here works on any Python 3.x).
Prerequisites
None — this is foundational Python, no external packages required. Useful next step: bio-sequence-manipulation-seq-objects for Bio.Seq objects once past raw strings.
Naming rules and conventions
Rules (breaking these is a SyntaxError):
- Names may contain letters, digits, and underscores; must start with a letter or underscore (not a digit).
- Names are case-sensitive (
gene and Gene are different variables).
- Python keywords (
if, for, class, ...) cannot be used as names.
Conventions:
snake_case for variables/functions: gene_name, sequence_length.
UPPER_CASE for constants: AVOGADRO = 6.022e23.
- Descriptive names:
gc_content, not gc or x.
# Good vs. bad variable names
sequence_length = 1500 # good: descriptive, snake_case
gene_name = "TP53"
melting_temperature = 65.5
x = 1500 # bad: meaningless
seqLen = 1500 # bad: camelCase, not snake_case
three = 1 # bad: misleading name for the value 1
# 2nd_gene = "EGFR" # SyntaxError: cannot start with a digit
Data Types Overview
| Type |
Example |
Bioinformatics use |
int |
42 |
Sequence length, read count, coordinate |
float |
0.487 |
GC content, E-value, p-value |
str |
"ATGCGA" |
DNA/RNA/protein sequence, gene name |
bool |
True |
Is valid? Has stop codon? Passed QC? |
NoneType |
None |
Missing annotation, unset default argument |
def describe_types(values):
"""Print the runtime type of each value in an iterable.
Mirrors a common bioinformatics debugging step: confirming a parsed
field (from a VCF, FASTA header, or config file) came back as the
expected type rather than a string.
"""
for v in values:
print(f"{str(v):25s} -> {type(v).__name__}")
describe_types([15_000_000, 0.52, "Escherichia coli", True, None])
# 15000000 -> int
# 0.52 -> float
# Escherichia coli -> str
# True -> bool
# None -> NoneType
Variables are references, not boxes
Goal: understand why aliasing a list/dict silently shares mutations, while aliasing a string/number does not.
Approach: inspect id() before and after reassignment vs. in-place mutation.
def show_reference_semantics():
"""Demonstrate immutable reassignment vs. mutable in-place mutation."""
# Strings are immutable: concatenation creates a NEW object
sequence = "ATGCGATCG"
before_id = id(sequence)
sequence = sequence + "AAA" # new string, sequence now points elsewhere
assert id(sequence) != before_id
# Lists are mutable: alias shares the SAME object
reads = ["read1", "read2"]
alias = reads # alias points to the same list
alias.append("read3")
assert reads == ["read1", "read2", "read3"] # mutation visible via original name
# Multiple-assignment gotcha: a = b = [] shares ONE list, not two
coverage_a = coverage_b = []
coverage_a.append(10)
assert coverage_b == [10] # surprising if you expected independence
show_reference_semantics()
Numeric types for bioinformatics
# int: unlimited precision, used for lengths, counts, coordinates
sequence_length = 3_088_286_401 # human genome length in bp
read_depth = 30 # sequencing coverage (30x)
print(f"Genome: {sequence_length:,} bp, target coverage: {read_depth}x")
# float: measurements, scores, probabilities
gc_content = 0.508 # GC fraction
e_value = 1.5e-42 # BLAST E-value, scientific notation
print(f"GC%: {gc_content * 100:.1f}% E-value: {e_value:.2e}")
# Floating-point precision: 0.1 + 0.2 != 0.3 exactly (binary representation)
result, expected, tolerance = 0.1 + 0.2, 0.3, 1e-9
is_close_enough = abs(result - expected) < tolerance # always compare with a tolerance
assert is_close_enough
String slicing and parsing (sequences, FASTA headers)
Goal: extract start/stop codons, reverse a sequence, and parse a FASTA header.
Approach: string[start:stop:step] — start inclusive, stop exclusive, negative step reverses.
def parse_orf(dna):
"""Extract start codon, stop codon, and coding region from a raw ORF string.
dna: full open reading frame including start (ATG) and a stop codon.
Returns a dict with start_codon, stop_codon, coding_region, reversed.
"""
return {
"start_codon": dna[0:3],
"stop_codon": dna[-3:],
"coding_region": dna[3:-3],
"has_valid_stop": dna.endswith(("TAA", "TAG", "TGA")),
"reversed": dna[::-1],
}
orf = parse_orf("ATGAAACCCGGGTAA")
assert orf["start_codon"] == "ATG"
assert orf["stop_codon"] == "TAA"
assert orf["coding_region"] == "AAACCCGGG"
assert orf["has_valid_stop"] is True
def parse_fasta_header(header):
"""Parse a UniProt-style FASTA header: '>db|accession|entry_name description'."""
parts = header.lstrip(">").split("|")
db, accession, rest = parts[0], parts[1], parts[2]
entry_name = rest.split()[0]
return {"database": db, "accession": accession, "entry_name": entry_name}
result = parse_fasta_header(">sp|P04637|P53_HUMAN Cellular tumor antigen p53")
assert result == {"database": "sp", "accession": "P04637", "entry_name": "P53_HUMAN"}
# DNA -> RNA transcription via replace(); strip() for cleaning file lines
dna = "ATGCGATCG"
rna = dna.replace("T", "U")
assert rna == "AUGCGAUCG"
clean_line = " ATGCGATCG \n".strip()
assert clean_line == "ATGCGATCG"
Pitfalls
- Aliasing shares mutable objects:
alias = original does not copy a list/dict — both names reference the same object; mutating through one is visible through the other. Immutable types (str, int, float, tuple) don't have this problem.
a = b = [] creates ONE shared list, not two independent empty lists. Use a, b = [], [] for independence.
- Mutable default arguments: never write
def f(x=[]) — the default list is created once and shared across calls. Use def f(x=None) and set x = x or [] inside.
- Off-by-one errors: Python slicing is half-open
[start, stop), but genomic coordinates (GFF, 1-based VCF POS) are often 1-based inclusive — convert explicitly (gff_start - 1) before slicing a Python string.
- Deep vs. shallow copy:
list.copy() only copies the top level; nested structures (list of lists, dict of lists) need copy.deepcopy().
- Floating-point equality: never compare floats with
==; use abs(a - b) < tolerance.
See Also
bio-sequence-manipulation-seq-objects — move from raw strings to Bio.Seq objects.
bio-sequence-manipulation-sequence-slicing — deeper slicing patterns for sequences.
bio-sequence-manipulation-reverse-complement — building on string reversal shown here.
bio-sequence-io-read-sequences — reading real FASTA/FASTQ files instead of inline strings.
1---2name: python-bio-variables3description: Declare and manipulate Python variables and core data types (int, float, str, bool, None) for bioinformatics scripts — naming, mutability, references, string slicing/indexing of DNA/RNA/protein sequences. Use when writing beginner Python for biology, explaining variable assignment/reassignment, debugging aliasing or mutable-default-argument bugs, or parsing sequence strings and FASTA headers with slicing/split/join.4---56# Variables and Data Types78## When to Use9- Writing or reviewing introductory Python bioinformatics scripts that assign sequences, counts, or scores to variables.10- Explaining why `alias = original` doesn't copy a list/dict, or why `seq = seq + "AAA"` doesn't mutate the original string.11- Choosing between `int`, `float`, `str`, `bool`, `None` for genomic data (coordinates, GC content, E-values, sequences, missing annotations).12- Slicing/indexing DNA/RNA/protein strings (start/stop codons, reverse complement setup, coding regions).13- Parsing simple text formats (FASTA headers, delimited fields) with `split()`/`join()`/`strip()`.1415## Version Compatibility16Python ≥3.9 (f-strings, arbitrary-precision `int` — no special version dependency; syntax here works on any Python 3.x).1718## Prerequisites19None — this is foundational Python, no external packages required. Useful next step: `bio-sequence-manipulation-seq-objects` for `Bio.Seq` objects once past raw strings.2021## Naming rules and conventions2223**Rules** (breaking these is a `SyntaxError`):24- Names may contain letters, digits, and underscores; must start with a letter or underscore (not a digit).25- Names are case-sensitive (`gene` and `Gene` are different variables).26- Python keywords (`if`, `for`, `class`, ...) cannot be used as names.2728**Conventions:**29- `snake_case` for variables/functions: `gene_name`, `sequence_length`.30- `UPPER_CASE` for constants: `AVOGADRO = 6.022e23`.31- Descriptive names: `gc_content`, not `gc` or `x`.3233```python34# Good vs. bad variable names35sequence_length = 1500 # good: descriptive, snake_case36gene_name = "TP53"37melting_temperature = 65.53839x = 1500 # bad: meaningless40seqLen = 1500 # bad: camelCase, not snake_case41three = 1 # bad: misleading name for the value 14243# 2nd_gene = "EGFR" # SyntaxError: cannot start with a digit44```4546## Data Types Overview4748| Type | Example | Bioinformatics use |49|------------|---------------|---------------------------------------------|50| `int` | `42` | Sequence length, read count, coordinate |51| `float` | `0.487` | GC content, E-value, p-value |52| `str` | `"ATGCGA"` | DNA/RNA/protein sequence, gene name |53| `bool` | `True` | Is valid? Has stop codon? Passed QC? |54| `NoneType` | `None` | Missing annotation, unset default argument |5556```python57def describe_types(values):58 """Print the runtime type of each value in an iterable.5960 Mirrors a common bioinformatics debugging step: confirming a parsed61 field (from a VCF, FASTA header, or config file) came back as the62 expected type rather than a string.63 """64 for v in values:65 print(f"{str(v):25s} -> {type(v).__name__}")6667describe_types([15_000_000, 0.52, "Escherichia coli", True, None])68# 15000000 -> int69# 0.52 -> float70# Escherichia coli -> str71# True -> bool72# None -> NoneType73```7475## Variables are references, not boxes7677**Goal:** understand why aliasing a list/dict silently shares mutations, while aliasing a string/number does not.78**Approach:** inspect `id()` before and after reassignment vs. in-place mutation.7980```python81def show_reference_semantics():82 """Demonstrate immutable reassignment vs. mutable in-place mutation."""83 # Strings are immutable: concatenation creates a NEW object84 sequence = "ATGCGATCG"85 before_id = id(sequence)86 sequence = sequence + "AAA" # new string, sequence now points elsewhere87 assert id(sequence) != before_id8889 # Lists are mutable: alias shares the SAME object90 reads = ["read1", "read2"]91 alias = reads # alias points to the same list92 alias.append("read3")93 assert reads == ["read1", "read2", "read3"] # mutation visible via original name9495 # Multiple-assignment gotcha: a = b = [] shares ONE list, not two96 coverage_a = coverage_b = []97 coverage_a.append(10)98 assert coverage_b == [10] # surprising if you expected independence99100show_reference_semantics()101```102103## Numeric types for bioinformatics104105```python106# int: unlimited precision, used for lengths, counts, coordinates107sequence_length = 3_088_286_401 # human genome length in bp108read_depth = 30 # sequencing coverage (30x)109print(f"Genome: {sequence_length:,} bp, target coverage: {read_depth}x")110111# float: measurements, scores, probabilities112gc_content = 0.508 # GC fraction113e_value = 1.5e-42 # BLAST E-value, scientific notation114print(f"GC%: {gc_content * 100:.1f}% E-value: {e_value:.2e}")115116# Floating-point precision: 0.1 + 0.2 != 0.3 exactly (binary representation)117result, expected, tolerance = 0.1 + 0.2, 0.3, 1e-9118is_close_enough = abs(result - expected) < tolerance # always compare with a tolerance119assert is_close_enough120```121122## String slicing and parsing (sequences, FASTA headers)123124**Goal:** extract start/stop codons, reverse a sequence, and parse a FASTA header.125**Approach:** `string[start:stop:step]` — `start` inclusive, `stop` exclusive, negative `step` reverses.126127```python128def parse_orf(dna):129 """Extract start codon, stop codon, and coding region from a raw ORF string.130131 dna: full open reading frame including start (ATG) and a stop codon.132 Returns a dict with start_codon, stop_codon, coding_region, reversed.133 """134 return {135 "start_codon": dna[0:3],136 "stop_codon": dna[-3:],137 "coding_region": dna[3:-3],138 "has_valid_stop": dna.endswith(("TAA", "TAG", "TGA")),139 "reversed": dna[::-1],140 }141142orf = parse_orf("ATGAAACCCGGGTAA")143assert orf["start_codon"] == "ATG"144assert orf["stop_codon"] == "TAA"145assert orf["coding_region"] == "AAACCCGGG"146assert orf["has_valid_stop"] is True147148def parse_fasta_header(header):149 """Parse a UniProt-style FASTA header: '>db|accession|entry_name description'."""150 parts = header.lstrip(">").split("|")151 db, accession, rest = parts[0], parts[1], parts[2]152 entry_name = rest.split()[0]153 return {"database": db, "accession": accession, "entry_name": entry_name}154155result = parse_fasta_header(">sp|P04637|P53_HUMAN Cellular tumor antigen p53")156assert result == {"database": "sp", "accession": "P04637", "entry_name": "P53_HUMAN"}157158# DNA -> RNA transcription via replace(); strip() for cleaning file lines159dna = "ATGCGATCG"160rna = dna.replace("T", "U")161assert rna == "AUGCGAUCG"162clean_line = " ATGCGATCG \n".strip()163assert clean_line == "ATGCGATCG"164```165166## Pitfalls167- **Aliasing shares mutable objects:** `alias = original` does not copy a list/dict — both names reference the same object; mutating through one is visible through the other. Immutable types (`str`, `int`, `float`, `tuple`) don't have this problem.168- **`a = b = []` creates ONE shared list**, not two independent empty lists. Use `a, b = [], []` for independence.169- **Mutable default arguments:** never write `def f(x=[])` — the default list is created once and shared across calls. Use `def f(x=None)` and set `x = x or []` inside.170- **Off-by-one errors:** Python slicing is half-open `[start, stop)`, but genomic coordinates (GFF, 1-based VCF POS) are often 1-based inclusive — convert explicitly (`gff_start - 1`) before slicing a Python string.171- **Deep vs. shallow copy:** `list.copy()` only copies the top level; nested structures (list of lists, dict of lists) need `copy.deepcopy()`.172- **Floating-point equality:** never compare floats with `==`; use `abs(a - b) < tolerance`.173174## See Also175- `bio-sequence-manipulation-seq-objects` — move from raw strings to `Bio.Seq` objects.176- `bio-sequence-manipulation-sequence-slicing` — deeper slicing patterns for sequences.177- `bio-sequence-manipulation-reverse-complement` — building on string reversal shown here.178- `bio-sequence-io-read-sequences` — reading real FASTA/FASTQ files instead of inline strings.