All 24 modules are open from the start — nothing here is
locked, and nothing costs anything. Sign in so your progress, titles and
credentials stay with you, on every device you use.
Free forever, with your Google account. No password, no payment.
Two containers that exist because lists cannot do everything
A list is changeable and ordered, which is usually right and
occasionally exactly wrong. A tuple is a sequence that cannot be
changed, which is what makes it safe to use as a dictionary key and honest about
a record whose shape is fixed. A set throws away order and
duplicates, which sounds like a loss until the question is "have I seen this
before?".
Ready?
1
A Tuple Is a Sequence That Cannot Change
Round brackets, or often no brackets at all — it is the commas that make
a tuple.
point = (3, 4)
row = "T-1003", "high", 90 # brackets optional
one = ("only",) # the trailing comma is what makes it a tuple
row[0] # "T-1003"
len(row) # 3
row[1] = "low" # TypeError — tuples do not support item assignment
Everything you can do to read a list works: indexing, slicing,
len(), in, looping. Everything that would change
it does not exist.
That sounds like a list with features removed, and the point is what the
restriction buys:
It can be a dictionary key
Keys have to be unchangeable, so ("eu", "2026-09") works and ["eu", "2026-09"] raises.
Nobody can edit it behind you
The aliasing bug from the last module cannot happen, because there is no operation that would cause it.
It says the shape is fixed
A list means "any number of these". A tuple means "exactly these, in this order" — a row, a coordinate, a pair.
Quick check
What is ("only") — with brackets but no comma?
2
Unpacking Is Everywhere Once You See It
Putting several names on the left of an assignment takes a sequence apart
in one line:
ticket_id, priority, minutes = "T-1003,high,90".split(",")
a, b = b, a # the swap from module 1-02 was a tuple
for key, value in counts.items(): # so was this
Both of those were unpacking all along. The swap builds a tuple on the
right and takes it apart on the left; .items() hands over a
two-item tuple per pass and the for line unpacks it.
The count has to match exactly. Three names and four values raises
ValueError: too many values to unpack — which is a genuinely
useful failure, because it means a line of your input file had a comma in
it that you did not expect.
When you want "the first one and everything else", a star collects the
rest into a list:
parts[0], parts[1] and parts[2]
scattered through a function are three chances to use the wrong index.
Unpacking once, into names, means the rest of the code reads
priority rather than parts[1] — and the
ValueError tells you immediately if the shape was not what
you thought.
Quick check
a, b = "x,y,z".split(","). What happens?
3
A Set Gives Up Order to Gain Speed and Uniqueness
Curly braces like a dictionary, but with values rather than pairs. A set
holds each thing at most once and does not keep the order you added them
in.
seen = {"loops", "lists", "loops"} # {"loops", "lists"} — the duplicate is gone
tags = set(["a", "b", "a"]) # the usual way: from a list
empty = set() # {} would be an empty dictionary
Two things follow, and they are the only reasons to reach for one:
Deduplicating.set(items) removes
duplicates in one word. len(set(items)) is "how many
distinct".
Membership.x in some_set is a direct
lookup, the same as a dictionary key. x in some_list walks
the list. On ten items nobody notices; on a hundred thousand, checking
each of a hundred thousand against a list is the difference between a
second and an afternoon.
And the cost, which is real: a set has no order and no
positions. tags[0] raises. If you need a stable
order for output, ask for one with sorted(tags), which hands
back a list.
Loses the order
tags = set(raw.split(","))
Fine for "have I seen this", wrong if the file order was information.
Keeps it
for t in raw.split(","): if t not in seen: ...
A set alongside the list, used only for the check.
Quick check
You need the distinct tags, in the order they first appeared. What does sorted(set(tags)) give you?
4
Four Operations That Replace Four Loops
Comparing two collections is a loop with a condition in it, done four
slightly different ways. Sets make each of them one line.
a = {"loops", "lists", "errors"}
b = {"loops", "dicts"}
a | b # union — everything in either
a & b # intersection — in both
a - b # difference — in a but not b
a ^ b # symmetric — in one but not both
The named forms — a.union(b),
a.intersection(b), a.difference(b) — do the
same and read better in a line already busy with other punctuation.
The reason to know these is that "what changed between yesterday and
today" is a question you will be asked constantly, and it is exactly two
differences:
added = current - previous
removed = previous - current
kept = current & previous
Note the direction. a - b is not b - a, and
getting them the wrong way round produces a report that is confidently
backwards.
Which container, then?
List when order matters or duplicates are meaningful.
Tuple when the shape is fixed, or you need a key.
Set when you care about membership and uniqueness and
nothing else. Dictionary when each thing has something
attached to it.
Picking the container is most of the design. The right one turns a loop
with a condition into a single operator.
Quick check
Which tags were removed between last week and this week?
0 of 9 completed
Real Python runs right here in your browser — nothing to install, nothing
sent to a server. The interpreter downloads once the first time you press
Run, then stays cached.
01
To do
Round brackets, or often none at all — it is the commas that make a
tuple. Everything that reads a list works on one.
A list says "any number of these". A tuple says "exactly these, in this
order" — which is what a row of a file actually is.
Your task: build row with those three
values, then print the id, the minutes, the length, and whether
"high" is in it:
T-1003 90 3 True
your_code.py
PythonCtrl↵ to run
Hint
The minutes are a number, so no quotes. row[-1] reaches the last item, and `"high" in row` answers the last line by itself.
Output
02
To do
row[1] = "low" raises
TypeError: 'tuple' object does not support item assignment.
There is no method that would do it either — the whole point of the type
is that there is not.
What you do instead is build a new one. Slicing and + both
work on tuples exactly as they do on strings, and for the same reason:
row = row[:1] + ("low",) + row[2:]
Note the comma in ("low",). Without it those brackets are
just grouping, and you would be trying to add a plain string to a tuple.
Your task: run it, read the error, then rebuild
row with the priority changed to low:
('T-1003', 'low', 90)
your_code.py
PythonCtrl↵ to run
Hint
Take everything before position 1, add a one-item tuple holding "low", then everything from position 2 on. The trailing comma in ("low",) is what makes it a tuple.
Output
03
To do
Several names on the left of an assignment take a sequence apart in one
line:
ticket_id, priority, minutes = line.split(",")
This is worth doing at the moment data arrives.
parts[1] scattered through the code below is a chance to use
the wrong index every time it appears; priority is not.
The count must match exactly. Too many or too few values raises a
ValueError, which is a useful failure — it means the line
was not the shape you assumed.
Your task: unpack the line into three names, convert the
minutes, and print:
T-1003 high 91
The last line is the minutes plus one, to prove it is a number.
your_code.py
PythonCtrl↵ to run
Hint
line.split(",") gives three pieces, which matches the three names. The minutes arrive as text, so convert with int() before the arithmetic.
Output
04
To do
When you want the front of a sequence by name and everything else in one
go, a star collects the remainder into a list:
The starred name always ends up a list, even when it catches one
item or none. It can go at either end —
*body, last = lines is equally valid — but only one star is
allowed, because two would be ambiguous.
Your task: split the command line, take the command by
name and the rest into arguments, then print the command, the
arguments, and how many there are:
deploy ['staging', '--dry-run'] 2
your_code.py
PythonCtrl↵ to run
Hint
Put the star on the second name: command, *arguments = command_line.split(). Splitting with no argument cuts on whitespace.
Output
05
To do
A set holds each value at most once. Building one from a list is how you
remove duplicates:
distinct = set(items) len(set(items)) # how many distinct
A set has no order and no positions, so distinct[0] raises.
When output has to be stable, sorted() hands you back a list
in a predictable order.
Your task: from the raw tags, print how many there are in
total, how many are distinct, how many duplicates that means, and the
distinct ones sorted:
set(tags) removes the duplicates. len() on each gives the two counts, and their difference is the number dropped. sorted(distinct) gives a list you can print predictably.
Output
06
To do
x in some_list walks the list until it finds a match.
x in some_set goes straight there, the same way a dictionary
finds a key.
On ten items the difference is invisible. Checking each of a hundred
thousand things against a list of a hundred thousand things is the
difference between a second and an afternoon — and it is the same code
either way, so the container is the whole decision.
Your task: build a set of the known tags, then report on
three candidates. Print the set's size, then True or
False for each candidate, then the ones that were not known,
sorted:
4 True False False ['files', 'threads']
your_code.py
PythonCtrl↵ to run
Hint
Print len(known) first. Then loop over candidates, print `candidate in known`, and append to unknown when it is not there. Finish with print(sorted(unknown)).
Output
07
To do
Comparing two collections is a loop with a condition, four slightly
different ways. Sets make each of them one line:
a | b # union — in either a & b # intersection — in both a - b # difference — in a but not b a ^ b # in one but not both
Watch the direction. a - b is not b - a, and a
report that has them the wrong way round is confidently backwards.
Your task: work out what was added, what was removed, and
what stayed, then print each sorted, and finally the size of the union:
Added is in current and not in previous, so current - previous. Removed is the other way round. Kept is the intersection, current & previous. The union is current | previous.
Output
08
To do
Dictionary keys have to be unchangeable, which rules out lists and lets in
tuples. That is what makes a compound key possible:
sales[("eu", "09")] = 1750
Region and month together identify one figure, and neither alone does.
The alternative — a dictionary of dictionaries, or a string key like
"eu-09" that has to be split apart again — is more code and
more chances to disagree with itself.
Your task: total the sales by region and month.
Then print the totals for two specific cells and the number of cells:
1750 900 3
your_code.py
PythonCtrl↵ to run
Hint
The key is the tuple (region, month). Use the counting pattern with it: sales[key] = sales.get(key, 0) + int(amount).
Output
09
To do
This code deduplicates the queue and reports the next three jobs. It
deduplicates correctly and reports the wrong three, because
set() threw the order away and nothing downstream can get it
back.
A set is the right tool for "have I seen this". It is the wrong tool for
"what came first". When you need both — distinct, in original order — keep
the list and use a set only for the check:
seen = set() for job in queue: if job not in seen: seen.add(job) unique.append(job)
Your task: rewrite it so the duplicates are gone
and the queue order survives:
['build', 'test', 'deploy', 'notify'] 4
your_code.py
PythonCtrl↵ to run
Hint
Start unique as an empty list and seen as an empty set. Loop the queue in order; when a job is not in seen, add it to both.
Output
The tag audit
To do
Two exports of the content tags, a week apart. Somebody has to say what
changed — and the second export has a duplicate in it, because the tool
that produced it does not deduplicate.
Build these:
previous and current — a set of distinct tags from each export
duplicates_dropped — how many entries the current export lost to deduplication
added — in the current set and not the previous one
removed — in the previous set and not the current one
kept — in both
all_tags — every distinct tag across both
Then print exactly seven lines, with each group of tags sorted:
set(last_week.split(",")) for each side. The duplicates dropped is the length of this_week.split(",") less the length of current. Added is current - previous, removed is previous - current, kept is current & previous, and all_tags is current | previous. Sort each group before printing it.