Finishing a course and being able to use what it taught are different things, and the gap shows up a week later. Things you understood perfectly on the day turn out to be half-remembered, which is normal — and the cure is exactly what it sounds like: coming back and trying again, without rereading first.
Bookmark this page. Come back in about a week for the first set, and in about a month for the second. Those gaps are a sensible starting point, not a magic schedule; if you come back sooner or later, it still helps.
How to use it:
- Try each one from memory first. Reopening the lesson before you try skips the part that does the work.
- Use the hints to check, not to start. Each one names the lesson it comes from.
- Keep a list of the ones you needed help with. That list is precisely the set of lessons worth rereading — which is more useful than rereading all of them.
A week later
1. Predict both lines.
scores = {"ada": [90, 85], "alan": [70], "grace": [88, 92, 79]}
averages = {name: sum(s) / len(s) for name, s in scores.items()}
print(sorted(averages, key=lambda name: averages[name], reverse=True))
print(round(averages["grace"], 1))Check your prediction
['ada', 'grace', 'alan']
86.3A dictionary comprehension, then sorted with a key — sorting a dictionary
sorts its keys, and the key function decides the order. From Comprehensions
and Scope & Lambdas.
2. Fix. When the file doesn’t exist, this should print [] rather than
crash.
def load_lines(path):
with open(path) as file:
return [line.strip() for line in file]
print(load_lines("missing.txt"))Check your fix
def load_lines(path):
try:
with open(path) as file:
return [line.strip() for line in file]
except FileNotFoundError:
return []
print(load_lines("missing.txt"))[]Catch the one exception you expect, and decide what “no file” should mean. From Reading & Writing Files and Errors & Exceptions.
3. Write long_words(text): it returns a dictionary of each word longer
than three letters, lower-cased, mapped to its length. This should print
{'quick': 5, 'brown': 5}:
# def long_words(text): ...
print(long_words("The Quick brown fox"))Check your answer
def long_words(text):
return {word.lower(): len(word) for word in text.split() if len(word) > 3}
print(long_words("The Quick brown fox")){'quick': 5, 'brown': 5}A dictionary comprehension with a filter at the end. From Comprehensions.
4. Explain. No code: in two sentences, why does def add(item, items=[])
cause trouble, and what’s the fix?
Check your explanation
The default list is created once, when the function is defined, so every call
that relies on the default shares — and keeps adding to — the same list. Use
items=None and create a fresh list inside the function when it’s None.
From Defining Functions. If your explanation said the same thing in different words, that counts.
A month later
1. Predict both lines.
from dataclasses import dataclass
@dataclass
class Task:
title: str
priority: int
done: bool = False
tasks = [Task("email", 2), Task("report", 1), Task("lunch", 3, True)]
todo = [t.title for t in sorted(tasks, key=lambda t: t.priority) if not t.done]
print(todo)
print(tasks[0])Check your prediction
['report', 'email']
Task(title='email', priority=2, done=False)Sorted by priority, then the done task filtered out. sorted makes a new list,
so tasks[0] is still the first task you wrote. From Dataclasses,
Comprehensions and Lists.
2. Fix. This should print each word that appears more than once — once
each, in order: ['a', 'b'].
seen = []
duplicates = []
for word in "a b a c b a".split():
if word in seen:
duplicates.append(word)
seen.append(word)
print(duplicates)Check your fix
seen = set()
duplicates = set()
for word in "a b a c b a".split():
if word in seen:
duplicates.add(word)
seen.add(word)
print(sorted(duplicates))['a', 'b']“Once each” is a uniqueness question, which is what sets are for — and in on
a set stays fast however long the text gets. From Sets & Choosing a
Collection.
3. Write a Budget class so that the code below prints the two lines
shown after it. spend must refuse — with a ValueError saying by how much —
any amount that would go over the limit.
# class Budget: ...
budget = Budget(100)
budget.spend(60)
try:
budget.spend(50)
except ValueError as error:
print("refused:", error)
print(budget.remaining())refused: over budget by 10
40
Check your answer
class Budget:
def __init__(self, limit):
self.limit = limit
self.spent = 0
def spend(self, amount):
if self.spent + amount > self.limit:
raise ValueError(f"over budget by {self.spent + amount - self.limit}")
self.spent += amount
def remaining(self):
return self.limit - self.spent
budget = Budget(100)
budget.spend(60)
try:
budget.spend(50)
except ValueError as error:
print("refused:", error)
print(budget.remaining())refused: over budget by 10
40The check comes before the change, so a refused amount leaves the budget as it was. From Classes & Objects and Errors & Exceptions.
4. Explain. No code: you’ve written a function. Name four kinds of case worth testing it with.
Check your explanation
A typical case; the boundaries — zero, empty, one; something invalid, and the error it should raise; and any bug you’ve ever fixed in it, so it can’t come back. From Testing Your Code.
After that
If these came easily, the best thing you can do next is 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 rereading three lessons on purpose beats rereading all of them.