Python Concepts Review: From Variables to APIs
Every tool you have met, sorted back into drawers
In this lesson
You have covered a lot of ground. This lesson does not teach anything new: it sorts what you already know into drawers, so that when a problem turns up you can find the right tool instead of the one you used most recently.
Every entry below names the question the concept answers, because that is how you will actually recall it. Nobody thinks “I need a dictionary”; they think “I need to look something up by name”.
Explain it like I’m 5
You have filled a toolbox by picking up one tool at a time. This lesson lays them all out on the bench and labels the drawers.
The core language: five ideas everything else sits on
Units 1 to 3 gave you the whole foundation, and it really is only five ideas.
- Values and variables — a name pointing at a value. Answers: how do I keep something to use later? (Variables)
- Types — text, whole numbers, decimals, true/false. Answers: what kind of thing is this, and what can I do with it? (Data Types)
- Control flow —
ifto choose,forandwhileto repeat. Answers: how do I decide, and how do I do it again? (if Statements, for Loops) - Functions — a named block that takes input and returns a result. Answers: how do I stop writing this out three times? (Functions)
- Files — reading and writing text on disk. Answers: how does anything survive the program ending? (Reading and Writing Files)
Almost every later topic is one of these five with a sharper edge on it. A generator is a function that pauses. A dataclass is a type you defined. A context manager is a try/finally with a name.
# The whole foundation in nine lines.
temperatures = [18.2, 21.0, 19.4, 24.8, 22.1]
def describe(readings):
"""Return a one-line summary of some numbers."""
average = sum(readings) / len(readings)
verdict = "warm" if average > 20 else "cool"
return f"{len(readings)} readings, average {average:.1f}, {verdict}"
print(describe(temperatures))
5 readings, average 21.1, warm
The four collections, and how to choose
This is the decision people get wrong most often, so it is worth a table rather than a paragraph. All four hold several things at once; they differ in what they are good at.
- List
[1, 2, 3]— ordered, changeable. The default. Use it when order matters or you keep appending. - Dictionary
{"a": 1}— look a value up by a key. Use it whenever you catch yourself searching a list for a match. - Set
{1, 2, 3}— no duplicates, unordered, very fast membership tests. Use it for de-duplicating and for “is this in the list of 50,000?” - Tuple
(1, 2)— like a list, but cannot be changed. Use it for a fixed record such as a coordinate, or when you want to be sure nothing edits it.
The rule of thumb: reach for a dictionary sooner than feels natural. Scanning a list to find a match is the most common avoidable slowness in beginner code, and a dictionary replaces the whole loop with one lookup.
people = [("ada", 36), ("sam", 41), ("rae", 29)]
# Searching a list: a loop, every time you need a name.
def age_of_slow(name):
for person, age in people:
if person == name:
return age
return None
# The same data as a dictionary: one lookup, no loop.
ages = dict(people)
print(age_of_slow("sam"), ages["sam"])
print(age_of_slow("kim"), ages.get("kim", "unknown"))
print("names:", sorted(set(ages)))
41 41 None unknown names: ['ada', 'rae', 'sam']
Choose the right collection for each storage need. Replace each "?" with one of "list", "dict", "set", or "tuple". Ask yourself two questions each time: does the order matter, and am I looking things up by name?
NEEDS = {
"the steps of a recipe, in order": "?",
"look up a capital city by country name": "?",
"strip duplicate email addresses out of a mailing list": "?",
"an (x, y) point that must never be edited": "?",
"a queue of jobs you keep appending to": "?",
"check whether a word is in a 50,000-word banned list": "?",
}
for need, choice in NEEDS.items():
print(f"{choice:6s} {need}")
Order matters twice (both are lists). “Look up by name” is the dictionary. Duplicates and fast membership are both the set. “Must never be edited” is the tuple.
NEEDS = {
"the steps of a recipe, in order": "list",
"look up a capital city by country name": "dict",
"strip duplicate email addresses out of a mailing list": "set",
"an (x, y) point that must never be edited": "tuple",
"a queue of jobs you keep appending to": "list",
"check whether a word is in a 50,000-word banned list": "set",
}
for need, choice in NEEDS.items():
print(f"{choice:6s} {need}")
list the steps of a recipe, in order
dict look up a capital city by country name
set strip duplicate email addresses out of a mailing list
tuple an (x, y) point that must never be edited
list a queue of jobs you keep appending to
set check whether a word is in a 50,000-word banned list
Making a program hold up (Units 4, 9)
These are the habits that separate a script that worked once from one you can leave running.
- try/except — answers: what should happen when this goes wrong? Catch the specific exception, not everything.
- Logging — answers: what did the script do at 3am while I was asleep? A log is a journal;
print()is a shout across the room. - Type hints — answers: what is this function supposed to be given? Documentation a checker can verify.
- Dataclasses — answers: how do I stop passing five loose variables around? A dataclass names the record.
- Classes — answers: which data and which behavior belong together?
from dataclasses import dataclass
@dataclass
class Reading:
station: str
celsius: float
def parse(line: str) -> Reading | None:
"""Turn 'oxford,18.2' into a Reading, or None if the line is unusable."""
try:
station, value = line.split(",")
return Reading(station.strip(), float(value))
except ValueError:
return None
for line in ["oxford,18.2", "leeds,not-a-number", "york,21.0"]:
print(line, "->", parse(line))
oxford,18.2 -> Reading(station='oxford', celsius=18.2) leeds,not-a-number -> None york,21.0 -> Reading(station='york', celsius=21.0)
The power tools, and what each one deletes
Unit 10 covered the features that exist to remove repetition. Each one is best remembered by the complaint it answers.
- Generators — “this loads a 4 GB file into memory.” A generator hands out one item at a time.
- Iterators — the protocol underneath every
forloop; write one when an object should be loopable. - Decorators — “I pasted this timing code into six functions.” A decorator wraps them instead.
- Context managers — “I keep forgetting to close things.” A context manager cleans up even when the code raises.
- Comprehensions — “three lines to build one list.”
The matching warning from that unit still applies: plain, obvious code beats a clever abstraction used once. Wrap it when the same wrapper shows up a third time.
The domains: what you can actually build
The last five units were less about the language and more about what to point it at.
- Automation (Unit 5) — rename, sort, back up, and tidy real files, with a dry-run mode first.
- Web apps (Unit 6) — Flask routes, templates, and forms.
- APIs and JSON (Unit 7) — call a service, page through the results, and flatten the response into clean records.
- Databases and regex (Unit 8) — store what you fetched so it survives, and pull structure out of messy text.
- Concurrency (Unit 11) — stop waiting on one thing at a time.
- Data and models (Units 12-13) — pandas, charts, and a first prediction.
- Games and GUIs (Unit 14) — event loops, and keeping logic apart from the screen.
One script, most of the toolbox
Concepts studied in isolation stay in isolation. Here is a single realistic function using seven of them at once, the shape of nearly every tool you will build from here.
from dataclasses import dataclass
@dataclass
class Sale:
region: str
amount: float
RAW = [
{"region": "north", "amount": "120.50"},
{"region": "south", "amount": "89.00"},
{"region": "north", "amount": "oops"},
{"region": "south", "amount": "215.75"},
]
def clean(rows):
"""Turn messy records into Sale objects, skipping anything unusable."""
for row in rows:
try:
yield Sale(row["region"], float(row["amount"]))
except (KeyError, ValueError):
print(" skipped:", row)
totals = {}
for sale in clean(RAW):
totals[sale.region] = totals.get(sale.region, 0) + sale.amount
for region, total in sorted(totals.items()):
print(f"{region}: {total:.2f}")
skipped: {'region': 'north', 'amount': 'oops'}
north: 120.50
south: 304.75
Common mistake: Treating forgotten syntax as proof you did not learn it
Reading a lesson feels like knowing, so not recalling it later feels like losing something.
You never had it memorized, and you do not need it. Aim to recognize which tool a problem calls for. Looking up the exact arguments is what documentation is for, and everyone does it.
Common mistake: Reviewing by re-reading instead of by building
Re-reading is comfortable and feels productive.
Close the page and write the thing from memory. The gaps you hit in five minutes of writing are worth an hour of re-reading, because they are the parts you actually do not know.
Common mistake: Scanning a list where a dictionary belongs
Lists are the first collection you learn, so they become the default for everything.
If you find yourself looping to find a matching item, you want a dictionary keyed on the thing you are matching. One lookup replaces the whole loop.
Common mistake: Skipping review straight after finishing a project
The project works, so it feels finished.
The half hour just after it works is when the lessons are sharpest. Write down what you had to look up — that list is your personal revision plan.
Which concept is for repeating work over many items?
Loops repeat. A comprehension is a loop with a shorter spelling, useful when the body is one expression.
You need to look a price up by product name. Which collection?
Looking up by key is the exact job a dictionary exists for, and it stays fast however much data you add.
Which concept groups data together with the behavior that uses it?
A class bundles attributes and methods. A dataclass is the shorter spelling when the data is the point and there is little behavior.
What is the honest reason to reach for a generator?
Memory is the reason. Note the trade-off: a generator is exhausted after a single pass, unlike a list.
Mini exercise (easy)
Ten real tasks, one concept each. For every task, name the Python concept you would reach for first. This is the recognition skill the whole lesson is about, and it is the one that survives when the syntax fades.
Practice here. Fill in the missing piece and click Run to try your answer in place.
# Name the concept each task needs. Use exactly one of:
# "loop" "dictionary" "function" "class" "try/except" "comprehension"
# "generator" "decorator" "context manager" "dataclass"
TASKS = {
"do the same thing to every row in a file": "?",
"look up a price by product name": "?",
"reuse the same twenty lines in three places": "?",
"keep a customer's details together with the code that uses them": "?",
"carry on when one row in the file is malformed": "?",
"build a list of every name in upper case, in one line": "?",
"read a 4 GB log without loading it into memory": "?",
"time six functions without editing any of them": "?",
"guarantee the file is closed even if the code raises": "?",
"hold a fixed set of fields with a free __init__ and __repr__": "?",
}
for task, concept in TASKS.items():
print(f"{concept:16s} {task}")
print("answered:", sum(1 for concept in TASKS.values() if concept != "?"), "of", len(TASKS))
Read each task for its verb. “For every” is a loop. “Look up by” is a dictionary. “Without loading it all” is a generator. “Without editing them” is a decorator. “Even if it crashes” is a context manager.
# Name the concept each task needs. Use exactly one of:
# "loop" "dictionary" "function" "class" "try/except" "comprehension"
# "generator" "decorator" "context manager" "dataclass"
TASKS = {
"do the same thing to every row in a file": "loop",
"look up a price by product name": "dictionary",
"reuse the same twenty lines in three places": "function",
"keep a customer's details together with the code that uses them": "class",
"carry on when one row in the file is malformed": "try/except",
"build a list of every name in upper case, in one line": "comprehension",
"read a 4 GB log without loading it into memory": "generator",
"time six functions without editing any of them": "decorator",
"guarantee the file is closed even if the code raises": "context manager",
"hold a fixed set of fields with a free __init__ and __repr__": "dataclass",
}
for task, concept in TASKS.items():
print(f"{concept:16s} {task}")
print("answered:", sum(1 for concept in TASKS.values() if concept != "?"), "of", len(TASKS))
loop do the same thing to every row in a file
dictionary look up a price by product name
function reuse the same twenty lines in three places
class keep a customer's details together with the code that uses them
try/except carry on when one row in the file is malformed
comprehension build a list of every name in upper case, in one line
generator read a 4 GB log without loading it into memory
decorator time six functions without editing any of them
context manager guarantee the file is closed even if the code raises
dataclass hold a fixed set of fields with a free __init__ and __repr__
answered: 10 of 10
assert TASKS["look up a price by product name"] == "dictionary", "looking up by key is the dictionary's whole job"
assert TASKS["read a 4 GB log without loading it into memory"] == "generator", "one item at a time is a generator"
assert TASKS["time six functions without editing any of them"] == "decorator", "wrapping a function without changing it is a decorator"
assert TASKS["guarantee the file is closed even if the code raises"] == "context manager", "cleanup that survives an exception is __exit__"
assert TASKS["carry on when one row in the file is malformed"] == "try/except", "carrying on after a failure is exception handling"
assert "?" not in TASKS.values(), "every task needs an answer"
assert len(set(TASKS.values())) == 10, "each concept should be used exactly once"
print("✓ Looks good!")