AmouAI Hub/Courses/Programming Fundamentals/Day 8
Lists & Tuples
The structure Week 1 kept making you want. Also the place where the box metaphor for variables finally breaks.
By the end of today you can
- Build, index, slice and grow a list
- Explain why indices sit between items, and stop making off-by-one errors
- Say what mutability actually means, at the level of objects and names
- Predict when two names share one object, and when they do not
- Copy a list on purpose, three different ways
- Write a comprehension, and say when a loop is clearer
- Pack and unpack tuples, including in a
forloop - Choose list or tuple for a given job, and defend the choice
▶Today's videos
Watch each video, then work the matching sections below. Watching alone will not do it.
b = a is not a copy · aliasing demonstrated in memory · three ways to copy, and why all three are shallow · the mutable default argument.enumerate · returning several values · refactoring number_analyzer.py onto a real list, output unchanged.1The structure you have been missing
Mini-Project 1 wanted this and could not have it.
On Day 5 you kept numbers in a comma-separated string, because you had nowhere else to put them. Yesterday's refactor made that workaround impossible to ignore — four functions all splitting the same text. Here is what you actually wanted:
Notice what did not change: the function still takes the numbers and a threshold, and still returns a count. The parsing, the empty-piece guard and the float() conversion all disappeared — they were never part of the idea, only of the workaround.
The operations
scores = [23, 45, 12, 67]
print(scores[0]) # first
print(scores[-1]) # last
print(len(scores))
scores.append(89) # add one to the end
scores.insert(0, 5) # add at a position
removed = scores.pop() # take the last one off
print(scores)
print(removed)
print(67 in scores)append, insert, sort and reverse change the list in place and return None. So scores = scores.append(5) throws your list away and leaves you with None. This is yesterday's return-versus-print lesson wearing a new hat — and it is the single most common list bug.
2Slicing, and the fence-post model
Indices sit between items, not on them.
Off-by-one errors come from imagining that index 2 is the third item. A better model: the index marks the gap before an item. A slice takes everything between two gaps.
The slice xs[a:b] includes a, excludes b, and its length is b - a. That single sentence eliminates most off-by-one errors — and it is why range(len(xs)) gives exactly the valid indices.
Slicing never raises IndexError, even when the bounds are nonsense — scores[10:99] on a four-item list is just []. Plain indexing does raise. That asymmetry catches people, because a bug in the bounds fails silently.
3Mutability, and where the box metaphor breaks
The most important idea in Week 2.
Until today you could think of a variable as a box holding a value. That model has been quietly wrong all along, and lists are where it breaks visibly.
A name does not contain an object. A name points at one. Two names can point at the same object — and if that object is mutable, changing it through one name changes what you see through the other.
Three ways to copy
original = [1, 2, 3]
copy_a = original[:]
copy_b = list(original)
copy_c = original.copy()
copy_a.append(4)
print(original, copy_a)They build a new outer list whose items still point at the same inner objects. For a list of numbers that is fine. For a list of lists it is not — mutating an inner list still shows through both. When you need a genuinely independent nested structure, use copy.deepcopy.
The mutable default argument
Yesterday I flagged this forward. Here it is.
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basketTuples: the immutable sibling
List [1, 2] | Tuple (1, 2) | |
|---|---|---|
| Can you change it? | Yes | No — TypeError |
| Use it as a dict key? | No | Yes (Day 9) |
| Signals to a reader | "this will grow" | "this is a fixed record" |
| Typical use | A collection of similar things | One thing with several fields |
A tuple is not "a list you cannot change" — it is a different message. (x, y) is one point. [p1, p2, p3] is a collection of points. Choosing the right one documents your intent for free.
4Comprehensions
The loop you write most often, in one line.
Three quarters of the loops you have written so far do the same thing: start with an empty list, walk something, append a transformed or filtered item. Python has a shorthand for exactly that shape.
[ what to keep for each item in what if condition ]
[ word.upper() for word in words if len(word) > 3 ]A comprehension should fit on one line and do one thing. If you find yourself with two for clauses and a nested if, or reaching past 80 characters, write the loop — it is not a failure, it is a readability decision. Comprehensions that need a comment to explain them have already lost.
5Packing and unpacking
Several values at once, without indices.
point = (3, 4)
x, y = point # unpack
print(x, y)
a, b = 1, 2 # pack then unpack
a, b = b, a # swap, no temp variable
print(a, b)
first, *rest = [10, 20, 30, 40]
print(first, rest)The swap on line 6 is worth pausing on. The right-hand side is fully evaluated first, into a temporary tuple, and only then unpacked into the names. That is why no temporary variable is needed.
Unpacking in a for loop
people = [("Amin", 34), ("Ada", 36)]
for name, age in people: # unpack each tuple
print(f"{name} is {age}")
for i, name in enumerate(["a", "b"]): # index AND value
print(i, name)range(len(...))If you ever write for i in range(len(xs)): just to reach xs[i], use enumerate instead — or drop the index entirely and iterate the items directly. Indices are for when you genuinely need the position, and most of the time you do not.
Returning several values
def stats_of(numbers):
"""Return count, total and mean."""
total = 0
for n in numbers:
total += n
count = len(numbers)
return count, total, total / count
count, total, mean = stats_of([12, 7, 25, 3])
print(count, total, mean)Python has no special "multiple return" feature. return a, b, c builds a tuple and hands it back; the call site unpacks it. Once you see that, the feature stops being magic — and you know you can hold the whole tuple instead if you would rather.
Refactor number_analyzer.py a second time, replacing the comma-separated string with a real list. The rule is the same as Day 6: the output must not change. Then compare the two versions and count how many lines vanished.
>_Python playground
A real Python interpreter running inside your browser. Nothing is installed, nothing is uploaded, nothing can break.
input(), comma separated →
✓Exercise set
Checked automatically the moment you submit. Work top to bottom — each one assumes the last. Your answers are saved in this browser.
Day 9 — Dictionaries & Sets
Key-value modelling, fast membership, and a framework for choosing the structure.