A week from now, some of this course will have faded. That’s normal, and it’s the reason for this page: trying to recall something that has half gone, and then checking, is one of the most reliable ways to make it last.
Bookmark this page. Come back in about a week for the first set and about a month for the second. Those gaps are a sensible starting point, not a magic schedule.
- Try each one from memory first. Rereading a lesson before trying skips the part that does the work.
- Use the hints to check. Each names the lesson it comes from.
- Keep a list of the ones you needed help with. That’s your reading list — much shorter than the whole course.
A week later
1. Predict both lines. Two patterns that differ only in the order of their alternatives.
import re
print(re.findall(r'(\w+)=("[^"]*"|\S+)', 'a=1 msg="two words"'))
print(re.findall(r'(\w+)=(\S+|"[^"]*")', 'a=1 msg="two words"'))Check your prediction
[('a', '1'), ('msg', '"two words"')]
[('a', '1'), ('msg', '"two')]Alternation takes the first alternative that matches, and \S+ happily
matches "two on its own. Put the most specific alternative first. From When
a Regex Is the Right Tool.
2. Fix. This should find the user o'brien, and prints an SQL error
instead.
import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (name TEXT)")
conn.executemany("INSERT INTO users VALUES (?)", [("ada",), ("o'brien",)])
def find(name):
return conn.execute(f"SELECT name FROM users WHERE name = '{name}'").fetchall()
print(find("o'brien"))Check your fix
import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (name TEXT)")
conn.executemany("INSERT INTO users VALUES (?)", [("ada",), ("o'brien",)])
def find(name):
return conn.execute("SELECT name FROM users WHERE name = ?", (name,)).fetchall()
print(find("o'brien"))[("o'brien",)]The apostrophe ended the SQL string early. A ? placeholder sends the name as
a value, which can never become SQL — the same fix that stops an injection.
From Queries You Can Trust.
3. Write level_at_least(minimum), a factory that returns a filter. This
should print ['WARNING', 'ERROR']:
ORDER = ["DEBUG", "INFO", "WARNING", "ERROR"]
# def level_at_least(minimum): ...
keep = level_at_least("WARNING")
print([level for level in ORDER if keep(level)])Check your answer
ORDER = ["DEBUG", "INFO", "WARNING", "ERROR"]
def level_at_least(minimum):
def keep(level):
return ORDER.index(level) >= ORDER.index(minimum)
return keep
keep = level_at_least("WARNING")
print([level for level in ORDER if keep(level)])['WARNING', 'ERROR']The inner function remembers minimum after the factory has returned. From
Functions That Build Functions.
4. Explain. No code: what’s the difference between a refactor and a fix, and why should they never happen in the same step?
Check your explanation
A refactor changes how code is organised and leaves what it does exactly the same; a fix changes what it does. Done together, a change in the output can’t be traced to either one — and the safety net can’t tell a mistake in the refactor from the intended fix. From Read Before You Change.
A month later
1. Predict both lines. The first is the repair for Day 4’s bug hunt.
import json
adders = [lambda x, i=i: x + i for i in range(3)]
print([add(10) for add in adders])
print(json.dumps({404: 2, "ok": True}))Check your prediction
[10, 11, 12]
{"404": 2, "ok": true}A default value is evaluated when each lambda is made, so each keeps its own
i. And JSON keys are always strings — the integer 404 becomes "404". From
Functions That Build Functions and Summaries as JSON.
2. Fix. A level saved to the database should come back as a Level: this
should print ERROR True. It crashes.
import sqlite3
from enum import IntEnum
class Level(IntEnum):
INFO = 20
ERROR = 40
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE t (level INTEGER)")
conn.execute("INSERT INTO t VALUES (?)", (Level.ERROR,))
stored = conn.execute("SELECT level FROM t").fetchone()[0]
print(stored.name, stored is Level.ERROR)Check your fix
import sqlite3
from enum import IntEnum
class Level(IntEnum):
INFO = 20
ERROR = 40
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE t (level INTEGER)")
conn.execute("INSERT INTO t VALUES (?)", (Level.ERROR,))
stored = Level(conn.execute("SELECT level FROM t").fetchone()[0])
print(stored.name, stored is Level.ERROR)ERROR TrueAn IntEnum goes into SQLite as a plain number and comes back as one. Convert
it back at the edge, where it’s read. From Checkpoint: Levels Everywhere.
3. Write collect(names): fetch every name at once, and report a failed
fetch as "failed" instead of losing the rest. This should print
{'web': 3, 'db': 'failed', 'api': 3}:
import asyncio
async def fetch(name):
await asyncio.sleep(0.01)
if name == "db":
raise ConnectionError("unreachable")
return 3
# async def collect(names): ...
print(asyncio.run(collect(["web", "db", "api"])))Check your answer
import asyncio
async def fetch(name):
await asyncio.sleep(0.01)
if name == "db":
raise ConnectionError("unreachable")
return 3
async def safely(name):
try:
return await fetch(name)
except ConnectionError:
return "failed"
async def collect(names):
results = await asyncio.gather(*(safely(name) for name in names))
return dict(zip(names, results))
print(asyncio.run(collect(["web", "db", "api"]))){'web': 3, 'db': 'failed', 'api': 3}Handle each source’s expected failure inside that source’s task, and gather
keeps the order you asked in. From Timeouts and Failures.
4. Explain. No code: what makes a test able to fail? Give the two examples from Day 3.
Check your explanation
It has to use input that would expose the bug. A test of “results are sorted” needs records stored out of order — sorted input passes even with no sorting. A test of “a failed save stores nothing” needs the bad record after a good one — a bad first record passes with or without a transaction. From Testing with a Throwaway Database.
After that
If these came easily, build something: the briefs at the end of On Your Own are a good start. If some didn’t, you now know exactly which lessons to reread — and that list is worth more than a second pass through the whole course.