# Python Bio Variables

> 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.

- Skill: `pavel-kravchenko/python-bio-variables` (Agent Skill)
- Install (CLI): `npx skillmds@latest add pavel-kravchenko/python-bio-variables`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pavel-kravchenko/python-bio-variables/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: pavel-kravchenko (https://skillmd.com/u/pavel-kravchenko)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/pavel-kravchenko/python-bio-variables

---


# 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`.

```python
# 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  |

```python
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.

```python
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

```python
# 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.

```python
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.

