# Python Bio Operators

> Apply //, %, and 'in' operators to DNA/protein data — codon math, reading frames, GC precedence, stop-codon lookups. Use when computing GC content, finding reading frames, filtering by QC, or fixing precedence bugs.

- Skill: `pavel-kravchenko/python-bio-operators` (Agent Skill)
- Install (CLI): `npx skillmds@latest add pavel-kravchenko/python-bio-operators`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pavel-kravchenko/python-bio-operators/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-operators

---


# Python Operators for Bioinformatics

## When to Use

- Computing GC content, codon counts, or reading frames from sequence lengths/positions
- Filtering sequences by multiple QC criteria (length, GC%, ambiguous bases) in one expression
- Checking membership — is a codon a stop codon, is a base valid, is a key in a codon table
- Debugging a wrong GC% or Tm calculation caused by operator precedence (`g + c / total * 100`)
- Choosing `==` vs `is`, or `list` vs `set` membership, for repeated sequence/codon lookups

## Version Compatibility

Python ≥3.8 (f-strings). No external packages required — pure built-in operators.

## Prerequisites

- Basic Python: variables, strings, lists, dicts, sets, functions
- No other skill required; this is foundational for `python-bio-strings`, `python-bio-sequences`

## Codon Arithmetic and Reading Frames

**Goal:** Given a sequence length or a position, get the number of complete codons, leftover
bases, and which of the three reading frames a position falls in.

**Approach:** `//` (floor division) always returns an `int` and gives complete units; `/`
always returns a `float`. `%` gives the remainder, which doubles as the reading-frame index.

```python
def codon_stats(seq_length: int, position: int = 0) -> dict:
    """Return codon-counting stats for a sequence length and a query position.

    seq_length // 3  -> number of complete codons (int, not float — use // not /)
    seq_length % 3   -> leftover nucleotides after the last complete codon
    position % 3     -> reading frame (0, 1, or 2) that `position` falls in
    """
    return {
        "complete_codons": seq_length // 3,
        "leftover_nucleotides": seq_length % 3,
        "reading_frame": position % 3,
    }

stats = codon_stats(seq_length=1000, position=47)
print(stats)  # {'complete_codons': 333, 'leftover_nucleotides': 1, 'reading_frame': 2}
```

## GC Content and Multi-Criteria Sequence Filtering

**Goal:** Compute GC content without a precedence bug, then combine it with length and
ambiguous-base checks in a single readable boolean expression.

**Approach:** Parenthesize `(g + c)` explicitly — `*` and `/` bind tighter than `+`, so
`g + c / total * 100` silently computes `g + (c / total * 100)`. Use chained comparisons
(`a <= x <= b`) instead of `x >= a and x <= b`, and set literals for O(1) membership tests.

```python
def gc_content(sequence: str) -> float:
    """Return GC content as a percentage. Precedence-safe: (g + c) is parenthesized."""
    seq = sequence.upper()
    g, c = seq.count("G"), seq.count("C")
    total = len(seq)
    return (g + c) / total * 100 if total else 0.0


def passes_qc(sequence: str, has_ambiguous_bases: bool = False) -> bool:
    """Multi-criteria QC filter: length 500-3000 nt, GC 30-70%, no ambiguous bases."""
    seq_length = len(sequence)
    gc = gc_content(sequence)
    return (
        500 <= seq_length <= 3000
        and 30.0 <= gc <= 70.0
        and not has_ambiguous_bases
    )


def is_stop_codon(codon: str) -> bool:
    """O(1) membership test against a set literal (use a set, not a list, for lookups)."""
    return codon.upper() in {"TAA", "TAG", "TGA"}
```

## Primer Melting Temperature (Wallace Rule)

**Goal:** Estimate a short primer's melting temperature — a common downstream use of the
same arithmetic/precedence rules.

**Approach:** `Tm = 2*(A+T) + 4*(G+C)` for primers under 14 nt (Wallace rule).

```python
def melting_temperature(primer: str) -> float:
    """Estimate Tm (degrees C) for a short PCR primer using the Wallace rule."""
    p = primer.upper()
    a, t, g, c = p.count("A"), p.count("T"), p.count("G"), p.count("C")
    if len(p) < 14:
        return 2 * (a + t) + 4 * (g + c)
    return 64.9 + 41 * (g + c - 16.4) / (a + t + g + c)
```

## Pitfalls

- **`//` vs `/` for codons:** `seq_length // 3` gives complete codons (`int`). `/` always
  returns `float`, which breaks anything expecting an integer codon count or index.
- **`%` for reading frames:** `position % 3` gives the frame (0, 1, or 2); `seq_length % 3`
  gives leftover nucleotides — don't confuse the two uses of the same operator.
- **Precedence trap in GC content:** `g + c / total * 100` is wrong (division binds before
  `+`). Always write `(g + c) / total * 100`.
- **`==` vs `is`:** use `==` for value comparison; `is` only for `None`/`True`/`False`.
  Never write `if x == None` or `if my_list is [1, 2, 3]`.
- **`in` checks dict keys, not values:** `"ATG" in codon_table` checks keys. Use
  `"Met" in codon_table.values()` to check values (and it's O(n), not O(1)).
- **`in` with sets is O(1), with lists is O(n):** convert to a `set` before repeated
  membership tests (e.g. stop-codon or valid-nucleotide checks in a loop over a chromosome).
- **`and`/`or` short-circuit:** `passes_qc(seq) and log_pass(seq)` won't call `log_pass` if
  `passes_qc` is `False` — fine for filters, a bug if the right side has required side effects.

## See Also

- `python-bio-expressions` — building larger expressions and boolean logic
- `python-bio-strings` — string methods (`.count()`, `.upper()`, slicing) used above
- `python-bio-sequences` — sequence objects that wrap these operations
- `bio-primer-design-primer-basics` — full primer design beyond the Wallace-rule estimate

