Day 8 / 25 Lists & Tuples 0/0 exercises Exercises ↓

AmouAI Hub/Courses/Programming Fundamentals/Day 8

Week 2 · Functions & Data Structures · Day 8

Lists & Tuples

The structure Week 1 kept making you want. Also the place where the box metaphor for variables finally breaks.

Study time
4 hours
Reading
Think Python, Ch. 10 & 12
Focus
mutability · comprehensions · unpacking

By the end of today you can

  1. Build, index, slice and grow a list
  2. Explain why indices sit between items, and stop making off-by-one errors
  3. Say what mutability actually means, at the level of objects and names
  4. Predict when two names share one object, and when they do not
  5. Copy a list on purpose, three different ways
  6. Write a comprehension, and say when a loop is clearer
  7. Pack and unpack tuples, including in a for loop
  8. 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.

8A
Lists, Slicing & Mutability (120 min)
Building and growing a list · the fence-post model of slicing · why b = a is not a copy · aliasing demonstrated in memory · three ways to copy, and why all three are shallow · the mutable default argument.
8B
Comprehensions, Tuples & the Second Refactor (120 min)
Comprehensions and when a loop is clearer · tuples as fixed records · packing, unpacking and 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

lists.py
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)
23 67 4 [5, 23, 45, 12, 67] 89 True
The methods that return None

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 rule that removes the guesswork

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

copying.py
original = [1, 2, 3]

copy_a = original[:]
copy_b = list(original)
copy_c = original.copy()

copy_a.append(4)
print(original, copy_a)
[1, 2, 3] [1, 2, 3, 4]
All three are SHALLOW copies

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.

the_fix.py
def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

Tuples: the immutable sibling

List [1, 2]Tuple (1, 2)
Can you change it?YesNo — TypeError
Use it as a dict key?NoYes (Day 9)
Signals to a reader"this will grow""this is a fixed record"
Typical useA collection of similar thingsOne 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.

the shape of a comprehension
    [ what to keep   for each item   in what   if condition ]
    [ word.upper()   for word        in words  if len(word) > 3 ]
When a loop is the better answer

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.

unpacking.py
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)
3 4 2 1 10 [20, 30, 40]

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

enumerate.py
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)
Amin is 34 Ada is 36 0 a 1 b
This is the end of 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

multi_return.py
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)
4 47 11.75

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.

Today's work

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.

scratch.pypython not loaded
Values for input(), comma separated →
Output appears here. The first run takes a few seconds while Python loads.

Exercise set

Checked automatically the moment you submit. Work top to bottom — each one assumes the last. Your answers are saved in this browser.

All Warm-up Core Challenge Reset day

Day 9 — Dictionaries & Sets

Key-value modelling, fast membership, and a framework for choosing the structure.

Continue →