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.
You have already met lists without being introduced:
split() hands one back every time. This module makes them yours to
build and change. A list is the first container in the track, and the first
thing you have met that can be modified in place — which is most of its power
and both of its famous bugs.
Ready?
1
A List Is an Ordered, Changeable Sequence
Square brackets, values separated by commas. The items keep the order you
put them in, and they do not have to be the same type — though in
practice a list whose items mean different things is usually a sign that
something else was wanted.
tags = ["loops", "lists", "functions"]
tags[0] # "loops" — counting from zero
tags[-1] # "functions" — from the end
tags[1:] # ["lists", "functions"]
len(tags) # 3
"lists" in tags # True
All of that is exactly what strings did in module 1-04, and for the same
reason: both are sequences, so indexing, slicing,
len() and in work the same way on either.
Learning it once was the point.
Slicing a list hands back a new list. Indexing one item hands
back the item itself. So tags[0] is a string and
tags[0:1] is a list of one string — a distinction that looks
pedantic until it is the reason something crashes.
An index that does not exist raises
tags[9] on a three-item list is an
IndexError, not None and not an empty string.
A slice is more forgiving: tags[5:9] quietly hands back
[]. That asymmetry is worth knowing before it surprises
you at the end of a loop.
Quick check
What is len(["a", "b", "c"][1:])?
2
Adding, Removing, Replacing
Unlike a string, a list can be changed in place. That is what
mutable means, and it is why a list is what a loop builds
into.
append(x)
One item on the end. The workhorse.
extend(other)
Every item of another list on the end. append would have added the list itself as one item.
insert(i, x)
At a position. Everything after it shuffles up.
remove(x)
The first item equal to x. ValueError if there is none.
pop()
Takes the last item off and hands it back. pop(0) takes the first.
queue = []
queue.append("order-1")
queue.append("order-2")
next_up = queue.pop(0) # "order-1", and the queue is now one shorter
queue[0] = "order-9" # replace in place
The important half: these methods change the list and hand back
None. tags = tags.append("x") throws your list
away and leaves tags holding nothing at all. Call the method;
do not assign its result.
Quick check
What does tags = tags.append("new") leave in tags?
3
Ordering and Summarising
Two ways to sort, and the difference matters:
scores.sort() # changes scores, returns None
ranked = sorted(scores) # leaves scores alone, returns a new list
sorted(scores, reverse=True) # biggest first
sort() is the in-place one and shares the None
trap with append. sorted() is the function that
hands back a new list, and it works on anything you can loop over, not
just lists.
Sorting compares items as they are, which means a list of numbers that
arrived as text sorts alphabetically:
["100", "20", "9"] sorts to
["100", "20", "9"], because "1" comes before
"2". Convert first.
Four built-ins summarise a list without a loop, and reading them is
faster than reading the loop they replace:
sum(scores) min(scores) max(scores) len(scores)
The accumulator loop from the last module is still worth knowing — you
need it the moment the rule is anything other than these four — but when
one of them fits, use it.
Quick check
You need the original list untouched and a sorted copy. Which?
4
Two Bugs That Only Mutable Things Can Have
Aliasing.b = a does not copy a list. It
points a second name at the same one, and this is the moment the "a
variable is a label, not a box" idea from module 1-02 starts costing money:
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] — a changed too
When you want an independent copy, ask for one:
b = a.copy(), or the older b = a[:]. Both build
a new list holding the same items.
Mutating while looping. Removing items from the list you
are currently walking makes the loop skip things. The loop tracks a
position; take an item out and everything after it slides down one, into
a position the loop has already passed:
for name in names:
if name.startswith("test-"):
names.remove(name) # silently skips the next one
The fix is not a cleverer loop. Build a new list of the ones you are
keeping, and rebind the name at the end. That is also the shape
comprehensions replace in module 3-03.
Both bugs are quiet
Neither of these raises. Aliasing produces a list that is correct
somewhere else in the program; mutating while looping produces a
filter that misses roughly half of what it should have caught, in a
pattern that depends on where the matches happened to sit.
If a function takes a list and changes it, say so in its name — or copy
it first and hand a new one back.
Quick check
Why does removing items inside a for loop skip some of them?
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
Square brackets, values separated by commas. Indexing and slicing work
exactly as they did on strings, because both are sequences.
tags[0] # the first item tags[-1] # the last one tags[1:3] # a NEW list of items 1 and 2 len(tags) # how many
Note the difference between the second and third lines:
tags[0] hands back the item, tags[0:1] hands
back a list containing it.
Your task: print four lines — the first tag, the last
tag, a slice of the middle two, and the count:
loops errors ['lists', 'functions'] 4
your_code.py
PythonCtrl↵ to run
Hint
tags[0] and tags[-1] for the ends. The middle two are tags[1:3] — start at 1, stop before 3. len(tags) counts them.
Output
02
To do
append() puts one item on the end. Starting from an empty
list and appending inside a loop is the accumulator pattern again, with a
list instead of a number.
cleaned = [] for tag in tags: cleaned.append(tag.strip())
Your task: the tags arrive with inconsistent spacing.
Build cleaned, stripped and title-cased, then print the list
and how many there are:
['Loops', 'Lists', 'Functions'] 3
your_code.py
PythonCtrl↵ to run
Hint
Split raw on the comma first, then loop over the pieces and append tag.strip().title() to cleaned.
Output
03
To do
Four methods cover most of it. Three change the list and hand back
None; pop() is the exception — it removes an
item and gives it to you.
queue.append("x") # on the end queue.insert(0, "x") # at a position queue.remove("x") # the first one equal to x item = queue.pop(0) # take the first off and keep it
Your task: run the queue through four changes, in this
order: put order-3 on the end, put order-0 at
the front, take the front one off into next_up, and remove
order-2. Then print:
order-0 ['order-1', 'order-3'] 2
your_code.py
PythonCtrl↵ to run
Hint
append, then insert(0, ...), then next_up = queue.pop(0), then queue.remove("order-2"). Only pop hands something back — the others are called for their effect.
Output
04
To do
The single most common list mistake there is. append()
changes the list and returns None, so assigning its
result throws the list away and leaves the name holding nothing.
tags = tags.append("x") # tags is now None
The same is true of sort(), insert(),
remove(), extend() and reverse().
They are called for their effect. Only pop() hands something
useful back.
The error usually arrives much later, somewhere else, as
TypeError: 'NoneType' object is not iterable — which is why
recognising the cause is worth more than reading that message.
Your task: fix the line so it prints the list:
['loops', 'lists', 'functions']
your_code.py
PythonCtrl↵ to run
Hint
Call the method and leave the name alone: tags.append("functions"). The list is changed in place, so there is nothing to assign.
Output
05
To do
scores.sort() reorders the list in place and returns
None. sorted(scores) leaves the original alone
and hands back a new list.
When you need both the original order and a ranking — which is most
report code — sorted() is the one.
reverse=True puts the biggest first.
Your task: build ranked, highest first,
without disturbing scores. Print both:
[88, 54, 92, 71] [92, 88, 71, 54]
your_code.py
PythonCtrl↵ to run
Hint
sorted(scores, reverse=True) builds a new list. Using scores.sort() would reorder the original and hand back None.
Output
06
To do
Sorting compares the items as they are. A list of numbers that arrived as
text is a list of text, so it sorts alphabetically: every string
starting with 1 comes before every string starting with
2, whatever the numbers mean.
sorted(["100", "20", "9"]) # ['100', '20', '9']
No error, no warning — a ranking that is confidently in the wrong order.
Convert first, then sort.
Your task: print the wrong sort, then build
numbers by converting each piece, and print the right one:
['100', '20', '9'] [9, 20, 100]
your_code.py
PythonCtrl↵ to run
Hint
Loop over raw and append int(piece) to numbers. Then sorted(numbers) compares numbers rather than characters.
Output
07
To do
sum(), min(), max() and
len() each do in one word what an accumulator loop does in
four lines — and they read faster, which is the real argument.
The loop is still the tool the moment the rule is anything other than
these. But when one fits, reaching past it is just more code to get wrong.
Your task: print a five-line summary of the scores, with
the average to two decimal places:
len, sum, min and max, then sum(scores) / len(scores) for the average with a {:.2f} spec.
Output
08
To do
backup = original does not copy anything. It points a second
name at the same list — the "a variable is a label, not a box" idea from
module 1-02, finally with teeth.
a = [1, 2] b = a b.append(3) print(a) # [1, 2, 3]
Strings never behaved like this because a string cannot be changed at all.
A list can, and every name pointing at it sees the change.
When you want an independent list, ask for one: a.copy(), or
the older a[:].
Your task: make backup a genuine copy, taken
before the change, so the output is:
['a', 'b', 'c'] ['a', 'b']
your_code.py
PythonCtrl↵ to run
Hint
backup = original.copy() builds a new list holding the same items, so appending to one leaves the other alone.
Output
09
To do
Removing items from the list you are currently walking makes the loop skip
things. The loop advances by position; take one out and everything after
it slides down into a position already visited.
The code below is meant to drop every test- account and
leaves one behind. It raises nothing, and how many it misses depends on
where they happened to sit.
Your task: rewrite it to build a list of the ones you are
keeping, and point names at that. Do not remove from a list
while looping over it.
['kenji', 'ada', 'grace']
your_code.py
PythonCtrl↵ to run
Hint
Start a new empty list, loop over the original, and append the names you want to keep. Assign that list to names at the end.
Output
The order book
To do
A supplier's order file has landed. Read it once, drop what should not be
counted, and report on what is left.
Each real line is sku,name,quantity,unit price. Blank lines and
lines starting with # are not orders. Neither is a line whose
quantity is 0 — that item is out of stock and must not appear
anywhere in the report.
Build these:
names — the names of the in-stock items, in file order
line_totals — quantity times unit price for each, in the same order
order_value — the whole order
biggest — the name of the item with the largest line total
ranked — the line totals, largest first, leaving line_totals in file order
Loop over raw.splitlines(), strip each line, and continue past the blanks and the # lines. Split what is left on the comma into four pieces; the quantity and price need int(). Skip a quantity of 0 with another continue. Append the name and the line total to their lists together, so the positions stay in step. For the biggest, line_totals.index(max(line_totals)) gives you the position, and names at that position gives you the name.