Last Session — A Quick Recap

You can already…
  • ✓Lists — hold many DNA sequences, index & slice
  • ✓Strings are sequences of bases — slice out codons
  • ✓Dictionaries — use as a codon table, key → value
  • ✓Look up a value by its key — table["ATG"]

…so you translated three codons by hand:

seq   = "ATGGGTTAA"
table = {"ATG": "Met", "GGT": "Gly", "TAA": "Stop"}

codon1 = seq[0:3]            # "ATG"
codon2 = seq[3:6]            # "GGT"
codon3 = seq[6:9]            # "TAA"

aa1 = table[codon1]          # "Met"
aa2 = table[codon2]          # "Gly"
aa3 = table[codon3]          # "Stop"

print(f"{aa1}-{aa2}-{aa3}")  # Met-Gly-Stop

From Three Codons to a Whole Gene

Claudia facing a long viral gene to translate
Three was fine. Three hundred?

You translated three codons by writing a line for each. But a real gene has hundreds of codons — seq[0:3], seq[3:6], seq[6:9], seq[9:12]… by hand would take all day, and you'd slip up by codon 40.

The idea behind today

We need a way to tell the computer: do this same step for every codon, on your own. That's what control flow gives us — and it turns a page of repetitive lines into a few that run themselves.

This Session — Control Flow

So far your programs ran straight down the page, once. Control flow lets a program repeat work and make decisions — the two ingredients of a program that runs itself. We'll meet them one at a time, then combine them to translate a gene.

🔁
Loops
for codon in codons:

Do the same step to every item — no copy-paste, no matter how long the sequence.

🔀
Decisions
if codon in stops:

Let the program choose what to do — and decide for itself when to stop.

🧬
Translation
peptide.append(aa)

Put them together: walk through a viral genome and build its proteins, automatically.

Your First Loop — do this for every item

for virus in viruses:

The loop runs once for every item in the list (viruses). Each time round, the loop variable (here virus) holds the next item. You pick its name.

The colon and the indent

The for loop line always ends with a :, with the loop's body indented underneath — indentation tells Python what's inside the loop. To indent, type four spaces before a line of code.

⏳Python editor will load when you reach this slide.

Run it — four items in, four lines out. Add a virus and run again.

A Running Total — the accumulator pattern

Summing numbers: the shortcut

sum() totals a list of numbers in one step — you used it last session while calculating the mean. Here we'll use it to sum the size of some viral genomes.

Summing with an accumulator loop

Combine a for loop with an accumulator variable to sum numbers. Start at 0, then loop through the list, adding a number each time. A powerful pattern that works for more than just sums.

⏳Python editor will load when you reach this slide.

Both totals match — the loop is what sum() does for you.

Which Genome Is Bigger?

You just totalled those four numbers without really looking at them. Now look — the bacteriophage that infects bacteria dwarfs HIV. Ask most people and they'd pick the human virus:

Bacteriophage λ
48,502 bp ← a phage!
SARS-CoV-2
29,903 bp
HIV-1
9,719 bp
Hepatitis B
3,182 bp
Why? Genome economy

RNA viruses (SARS-CoV-2, HIV, flu) copy themselves without proofreading, so errors cap their genomes at roughly 30 kb. To survive, they pack genes tightly — even overlapping them. Phage λ is a DNA virus: accurate copying lets it afford a far bigger genome. Nasty to humans ≠ big.

Building a New List — the transform pattern

Empty list → loop → append

Set up an accumulator that's a list instead of a number: start with [ ], take each item from the old list, change it, and .append() it to the new one.

Remember this shape

Take each item → transform it → collect the results. Translating a gene is exactly this shape: take each codon, look up its amino acid, collect the protein. Hold onto this — we're heading there.

⏳Python editor will load when you reach this slide.

One list goes in, a new transformed list comes out — this is a pattern you'll see all the time in data analysis code.

Five questions

Total incubation time, one step at a time

Two loops from today: transform each incubation period into whole minutes, then accumulate them into one grand total.

# Mean incubation period, in days
# 229E, SARS, SARS-CoV-2, MERS, Omicron
incubation_days = [3.0, 4.7, 4.9, 5.8, 3.4]
/poll

Step 1 — Start the loop

Which line starts a loop over every incubation period?

incubation_days = [3.0, 4.7, 4.9, 5.8, 3.4]
Afor days in incubation_days
Bfor days in incubation_days:
Cfor incubation_days in days:
Dfor days = incubation_days:
/poll

Step 2 — Days to minutes

Inside the loop: which line turns one period into whole minutes? (1 day = 1440 minutes)

for days in incubation_days:
    # ???
Aminutes = days * 1440
Bminutes = round(days / 1440)
Cminutes = round(incubation_days * 1440)
Dminutes = round(days * 1440)
/poll

Step 3 — Collect the results

Still inside the loop: which line adds minutes to the new list?

incubation_minutes = []
for days in incubation_days:
    minutes = round(days * 1440)
    # ???
Aincubation_minutes = minutes
Bincubation_minutes.append(minutes)
Cincubation_minutes + minutes
Dminutes.append(incubation_minutes)
/poll

Step 4 — The running total

Where does total = 0 have to go?

for m in incubation_minutes:
    total = total + m
print(total)
ABefore the loop
BInside the loop
CAfter the loop
/poll

Step 5 — The grand total

What does the program print?

print(f"Total incubation time: {total} minutes")
A4896
B31392
C21.8
D523.2
/poll

Revision — Total Incubation Time

Try this after the lecture. Two loops, both from today — transform each incubation period into minutes, then accumulate them into one total.

1
Loop over incubation_days: convert each to whole minutes (× 1440, round()) and .append() it to incubation_minutes
2
Loop over incubation_minutes: add every value into total
⏳Python editor will load when you reach this slide.

Making a Decision

Comparing values

As we saw, RNA virus genomes are typically smaller than 30 kb, while DNA viruses can be bigger. Using if and comparison operators (> < == >=), we can compare our viral genome size to 30,000 bp to see if it's likely to be from an RNA virus.

if runs a block only when True

Put a condition after if, end the line with :, and indent the body — just like a loop. Those lines run only when the condition evaluates to True; if it's False, Python skips them.

⏳Python editor will load when you reach this slide.

Run it — then increase the value of genome above 30,000 and watch the message vanish.

Two Ways to Go — if / else

else — the other path

When the if condition is False, the else block runs instead. Exactly one of the two branches runs, every time.

No more “nothing happened”

With if alone, a False condition just does nothing. else guarantees the program always says something — RNA or DNA virus?

⏳Python editor will load when you reach this slide.

λ phage is above 30 kb — so this time the else branch runs.

Many Branches — if / elif / else

elif = “else, if…”

Python checks the conditions top to bottom. The first one that's True wins — the rest are skipped — and else catches everything left over.

The 30 kb line is real

Remember: RNA viruses cap out around 30 kb. So a genome past that line almost has to be a DNA virus like phage λ. The branches mirror the biology.

⏳Python editor will load when you reach this slide.

SARS-CoV-2 sits just under the 30 kb ceiling. What about hepatitis B (3,182 bp)? Change the number, rerun, watch the category change.

Decisions Inside a Loop

Count with a condition

Put an if inside the loop and bump an accumulator only when it's True. Now we can identify just the RNA viruses in our list by asking — which ones are under 30 kb?

Filter into a new list

It's the transform pattern with a bouncer at the door. The result? We .append() only the items that pass the test. The if makes a decision — the loop turns it into a flexible data-processing tool.

⏳Python editor will load when you reach this slide.

Three of the four — the question we asked at the start, answered automatically:

The Same Program, as a Flowchart

Five lines of code — but they don't run straight down. The colours show which line is which box on the right:

rna = []
for g in genomes:
if g > 10000:
rna.append(g)
print(rna)

The loop runs the decision once per genome — and only the ones that pass get kept.

rna = []start with an empty list
↓
for each genome g↻ repeat
g < 30000 ?
True ↓
rna.append(g)
False ↓
skip it
when the list runs out↓
print(rna)[29903, 9719, 3182]

Loops Aren't Just for Lists

Looping through a dictionary

Great — but we lost the names of the viruses along the way. Using a dictionary would help, and we can loop through it — just like a list. To count viruses <30 kb, we use .values() to get the sizes from our dictionary.

Using both keys and values

To get only viruses <30 kb, we need a loop that checks the sizes (values) and stores the names (keys). With .items(), we can process both keys and values in one loop, using for key, value in dict.items():

⏳Python editor will load when you reach this slide.

With a dictionary, we keep our virus names and sizes paired together as keys and values — helpful for processing data without getting confused.

Five questions

Ready to translate? One step at a time

To translate a sequence it must be a whole number of codons: its length divides exactly by 3. Loop through the dictionary, test each length, and collect the names of the viruses that pass.

sequences = {
    "SARS-CoV-2":  "ATTAAAGGTTTATACCTTCCCAGGTAACAA",
    "HIV-1":       "GGTCTCTCTGGTTAGACCAGATCT",
    "hepatitis B": "AATTCCACAACCTTCCACCA",
    "measles":     "ACCAAACAAAGTTGGGTA",
    "phage λ":     "GGGCGGCGACCTCGCG",
    "phiX174":     "GAGTTTTATCGCTTCCATGAC",
}
/poll

Step 1 — The remainder

Warm-up: % gives the remainder after dividing. What is 20 % 3?

print(20 % 3)
A6
B2
C6.67
D0
/poll

Step 2 — Loop through the dictionary

Which loop gives you each virus's name and its sequence?

Afor seq in sequences:
Bfor name, seq in sequences:
Cfor name, seq in sequences.items():
Dfor name, seq in sequences.values():
/poll

Step 3 — Test the length

Which test is True only for a whole number of codons?

Aif len(seq) % 3 == 0:
Bif len(seq) / 3 == 0:
Cif len(seq) % 3 = 0:
Dif seq % 3 == 0:
/poll

Step 4 — Collect the names

Inside the if: which line keeps the name of a virus that passes?

ready = []
for name, seq in sequences.items():
    if len(seq) % 3 == 0:
        # ???
Aready = name
Bready.append(seq)
Cready.append(sequences)
Dready.append(name)
/poll

Step 5 — How many are ready?

After the loop, what is len(ready)?

A6
B2
C4
D0
/poll

Revision — Ready to Translate?

Try this after the lecture. You've been handed real viral fragments. Before translating them, identify which ones have a whole number of codons — loop through the dictionary, test each length, and collect the names of the viruses in a new list. (A real pipeline would translate the sequences next.)

1
Loop through sequences.items(): test each sequence with len(seq) % 3 == 0
2
If it passes, .append() the virus name to ready
⏳Python editor will load when you reach this slide.

A String Is a Sequence — Recap

Everything you did to lists works on DNA

From Session 2: seq[0] is the first base, seq[0:3] slices out a codon, len(seq) counts the bases. Same square brackets, same rule — the end of a slice is not included.

New: a string loops too

for base in seq: works exactly like for g in genomes: — but a string hands you one character at a time. Twelve bases in, twelve lines out.

⏳Python editor will load when you reach this slide.

Run it — the loop gives you every base, one by one:

Why We Need range()

One base at a time isn't enough

for base in seq: hands you single letters — but a codon is three. And seq[::3] doesn't help: it grabs every third base on its own and throws the neighbours away.

range() — a run of numbers

range(start, stop, step) counts for you: range(0, 12, 3) gives 0, 3, 6, 9. Loop over positions instead of letters, and each i is where a codon starts — then seq[i:i+3] takes it plus its two neighbours.

⏳Python editor will load when you reach this slide.

Run it — the wrong way, then the numbers, then the payoff:

Carving Out Codons

range with a step

A third number makes range jump: range(0, len, 3) gives 0, 3, 6, 9… Each i is the start of the next codon.

Slice three bases

seq[i:i+3] grabs the codon at position i — the same slicing from Session 2. The - 2 stops us grabbing a half-codon off the end.

⏳Python editor will load when you reach this slide.

One sequence in — every codon out, in order:

Building the Protein

Three ideas, one loop

range gives the codon start · seq[i:i+3] carves it out · codon_table[codon] looks it up. Everything from Sessions 2 and 3, finally in one place.

The accumulator, again

Same pattern as the running total — but adding letters instead of numbers. Start empty, add one each time round. * is the stop codon: it's in the protein, and it shouldn't be. Next slide.

⏳Python editor will load when you reach this slide.

DNA in, protein out — your first real translator:

Stop and Go — break

in tests the stop set

The three stop codons live in a set: {"TAA", "TAG", "TGA"}. Then codon in stop_codons asks a yes/no question: is this codon one of the three? True or False, ready for an if.

break halts the loop

break leaves the loop immediately — the remaining codons are never read, and the stray * never reaches the protein. That's MKRGV, not MKRGV* — exactly what a ribosome does when it lets go.

⏳Python editor will load when you reach this slide.

The same translator — but it now knows where the protein ends:

Two Proteins, One Sequence

One 60-base stretch of real phiX174 DNA:

seq = "TATGGTACGCTGGACTTTGTGGGATACCCTCGCTTTCCTGCTCCTGTTGAGTTTATTGCT"
Frame 0 — read normally
for i in range(0, len(seq) - 2, 3):
YGTLDFVGYPRFPAPVEFIA

gene D · scaffold protein

Frame +1 — shift the start one base
for i in range(1, len(seq) - 2, 3):
MVRWTLWDTLAFLLLLSLL

gene E · lysis enzyme

Genome economy, made visible

Same loop, run twice — only the 0 became a 1 — and a completely different protein falls out of the same letters. This is how phiX174 packs more genes than it has room for. Overlapping genes were discovered here, in the first genome ever sequenced (Sanger, 1977). Remember the 30 kb ceiling? This is the trick that beats it.

Five questions

Two frames, two proteins

Everything from today, assembled: loop, range, slice, look up, accumulate. Build the translator step by step, then shift the frame.

# 60 real bases of phiX174, the first genome ever sequenced
seq = "TATGGTACGCTGGACTTTGTGGGATACCCTCGCTTTCCTGCTCCTGTTGAGTTTATTGCT"
codon_table = {"TTT": "F", "TTC": "F", ...}   # the full genetic code
frame = 0
/poll

Step 1 — Codon positions

Warm-up: which numbers does range(0, 12, 3) give?

for i in range(0, 12, 3):
    print(i)
A0, 3, 6, 9
B0, 3, 6, 9, 12
C3, 6, 9, 12
D0, 1, 2, … 11
/poll

Step 2 — Carve out the codon

Inside the loop: which slice gives the codon that starts at i?

for i in range(frame, len(seq) - 2, 3):
    codon = ???
Aseq[i:3]
Bseq[i:i+3]
Cseq[i+3]
Dseq[i, i+3]
/poll

Step 3 — Look it up

Which line gives the amino acid for codon?

Acodon_table(codon)
Bcodon_table[i]
Ccodon_table["codon"]
Dcodon_table[codon]
/poll

Step 4 — Build the protein

protein starts as "". Which line adds each amino acid on?

Aprotein = protein + codon_table[codon]
Bprotein = codon_table[codon]
Cprotein + codon_table[codon]
Dprotein.append(codon_table[codon])
/poll

Step 5 — Shift the frame

Set frame = 1 and run it again. Which protein falls out?

AYGTLDFVGYPRFPAPVEFIA
BMVRWTLWDTLAFLLLLSLL
CL
DATGGTACGCTGGACT…
/poll

Revision — Two Frames, Two Proteins

Try this after the lecture. You've just seen genes D and E fall out of the same 60 bases. This is the code that does it — everything from today, assembled: loop, range, slice, look up, accumulate. Make both proteins appear.

1
Loop with range(frame, len(seq) - 2, 3): carve each codon with seq[i:i+3], look it up, add it onto protein
2
Run it for frame 0, then change frame to 1 and run again
⏳Python editor will load when you reach this slide.

Recap — Programs That Run Themselves

You arrived able to translate three codons by hand. You're leaving with a translator that reads a whole gene on its own — and knows where to stop.

🔁
Loops
  • ✓for g in genomes: — repeat for every item
  • ✓The accumulator — total = total + g
  • ✓Build a new list with .append()
  • ✓range(start, stop, step) — loop over positions
🔀
Decisions
  • ✓Comparisons are just True / False
  • ✓if / elif / else — pick a branch
  • ✓if inside a loop — count and filter
  • ✓break — leave the loop the moment you’re done
🧬
The translator
  • ✓A string loops too — one base at a time
  • ✓seq[i:i+3] — carve out each codon
  • ✓codon_table[codon] — look it up
  • ✓Shift the frame — a different protein falls out

What's Next — Naming What You Built

Session 4 — algorithms, functions & errors

Notice what you just did: to read the second frame you changed a number and ran the whole thing again. That works for two frames. It won't work for two hundred genes. Next time we give your translator a name — and then it costs one line to use.

  • ▸Algorithms — plan the recipe before you write the code
  • ▸Functions — write the translator once, call it anywhere
  • ▸Errors — read a traceback and fix the bug yourself
  • ▸Stop copy-pasting code — start naming it
Between now and then

You can now write a program that loops, decides, and stops on its own — the three things that separate a script from a calculation. Everything after this is about doing it more cleanly.

  • →Re-open today’s Colab and rebuild the translator from scratch
  • →Try frame = 2 — is there a third protein hiding in there?
  • →Half an hour of practice beats re-watching the lecture

See you next week!