10a A List Is a Sequence, and Lists Are Mutable

This lesson introduces the list as a sequence whose elements can be of any type, shows that everything learned about string indexing and slicing carries over unchanged, and then introduces the one difference that changes everything: a list can be modified in place.

Subject: Python · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Lesson 10a A List Is a Sequence, and Lists Are Mutable

Title

Python · Chapter 10 — Lists

§10.1-10.5, pp. 89-91

2. By the end of this lesson you can

Objectives

Five things, each one you can check yourself at an interpreter prompt.

Think Python, 2nd edition — Allen B. Downey §10.1-10.5, pp. 89-91 — the pages these objectives are drawn from

3. Before we start: what could a string not hold?

Warm-up

Strings have carried you through two chapters. Find their limit.

Discussion prompt

A string is a sequence of characters. Name two things you might want to keep in a sequence that a string cannot hold, and say what goes wrong if you try.

Hint: Think about numbers, and about things that are themselves sequences.

Answer:

Numbers, for one: '42' is text, and doing arithmetic on it needs a conversion every time.

And a sequence of words, or of other sequences. A string can hold the characters of several words, but not the words as separate items — you would have to remember where each one started.

A list solves both. Like a string, a list is a sequence of values; in a string the values are characters, and in a list they can be any type at all.

4. The one idea behind this chapter: a sequence you can change

Concept

Like a string, a list is a sequence of values. Everything you learned about indexing, slicing, traversal and the in operator carries over unchanged — and then one thing is different, and it changes how you write nearly everything.

list — A sequence of values, which may be of any type, and which can be modified after it is created.

The values in a list are called elements, or sometimes items. Unlike strings, lists are mutable — and that single difference is the subject of the rest of the chapter.

Figure (svg): Two columns comparing strings and lists across the properties that matter

Three rows the same. The fourth is what this chapter is about.

Think Python, 2nd edition — Allen B. Downey §10.1-10.5, pp. 89-90

5. Making a list, and what can go in one

Section

Section 1

6. Square brackets, and any types you like

Concept

There are several ways to create a new list; the simplest is to enclose the elements in square brackets. The elements do not have to be the same type, and one of them may be another list.

>>> [10, 20, 30, 40]
[10, 20, 30, 40]
>>> ['crunchy frog', 'ram bladder', 'lark vomit']
['crunchy frog', 'ram bladder', 'lark vomit']
>>> ['spam', 2.0, 5, [10, 20]]
['spam', 2.0, 5, [10, 20]]
>>> []
[]
ExpressionWhat it containsNote
[10, 20, 30, 40]four integersall the same type
['spam', 2.0, 5, [10, 20]]a str, a float, an int, and a listmixed types
[]no elements at allthe empty list

A list within another list is nested. A list that contains no elements is called an empty list, and you create one with empty brackets.

Think Python, 2nd edition — Allen B. Downey §10.1-10.5, pp. 89-89

7. Picture it: the book's figure 10.1

Picture it

Lists are drawn as boxes with the word list outside and the elements inside.

Figure (svg): A state diagram showing three names pointing at three list objects: one with three strings, one with two numbers, and one empty

Three names, three list objects. The numbers under each element are its index.

This is the state diagram from lesson 2a with a new kind of thing on the right of the arrow — and in lesson 10c the fact that the arrow points at an OBJECT rather than a value becomes the whole story.

8. Worked example: a nested list counts as one element

Worked example

Predict the length before advancing. The nesting is the trap.

>>> t = ['spam', 1, ['Brie', 'Roquefort', 'Pol le Veq'], [1, 2, 3]]
>>> len(t)
4
ElementIts indexNote
'spam'element 0a string
1element 1an int
['Brie', ...]element 2a list — but ONE element
[1, 2, 3]element 3another list, also one element

Count the top-level items.

Why: Four things separated by commas at the outer level: a string, an integer, and two lists.

Notice what len counts.

Why: Although a list can contain another list, the nested list still counts as a single element. The length of this list is four.

Look inside if you need to.

Why: t[2] is the whole inner list, and t[2][0] is its first element — the bracket operator applied twice.

Figure (svg): The state of the program after each line of Worked example a nested list counts as one element, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

4. A nested list is one element of the outer list, however many things it contains itself.

Verify: Count what a flattened version would give.

Why: Flattened, the same data would be eight items. That len reports four rather than eight is what makes nesting a structure rather than a convenience — the shape carries information, and chapter 12 relies on it heavily.

9. Predict: what is the length?

Prediction

Count the top-level elements.

Predict first

What is len(['a', ['b', 'c'], 'd'])?

  • 2
  • 3
  • 4
  • 5

Correct: 3 — a string, a nested list, and another string, counted as three top-level elements.

Why: The nested list counts as a single element however many things it contains. This is what makes nesting a way of building structure: the outer list has three slots, and one of them happens to hold a list. Chapter 12's sequences of sequences depend entirely on this.

10. Worked example: everything from strings carries over

Worked example

Four operations from chapter 8, applied to a list unchanged.

>>> cheeses = ['Cheddar', 'Edam', 'Gouda']
>>> cheeses[0]
'Cheddar'
>>> cheeses[-1]
'Gouda'
>>> len(cheeses)
3
>>> 'Edam' in cheeses
True
ExpressionWhat it doesNote
cheeses[0]indexing from zerosame as a string
cheeses[-1]negative index from the endsame as a string
len(cheeses)the number of elementssame as a string
'Edam' in cheesesmembershipsame as a string

Index it.

Why: The syntax for accessing the elements of a list is the same as for accessing the characters of a string. Remember that the indices start at 0.

Use everything else you know.

Why: Any integer expression can be used as an index; an out-of-range index gives an IndexError; a negative index counts backward from the end.

Test membership.

Why: The in operator also works on lists — though note what it tests: whether a value is an ELEMENT, not whether it is a subsequence.

Figure (svg): A list of three cheese names drawn as boxes with positive indices above and negative indices below

Exactly the picture from lesson 8a, with strings in the boxes instead of characters.

All four behave exactly as they did for strings. Chapter 8's whole vocabulary transfers with nothing changed.

Verify: Check one place where in behaves differently.

Why: For strings, 'na' in 'banana' is True because it tests substrings. For lists, in tests membership of a single element — so ['a','b'] in ['a','b','c'] is False, because no element equals that list. Same operator, subtly different question, and worth confirming rather than assuming.

11. Trap: expecting in to find a sublist

Trap

The trap

A student writes [1, 2] in [1, 2, 3] expecting True, by analogy with 'ab' in 'abc'.

Carry the string behaviour across unchanged

Why: Nearly everything else did, so it is a reasonable expectation.

For lists, in tests whether a value is an ELEMENT. No element of [1, 2, 3] is the list [1, 2], so the answer is False.

The fix

in asks about elements for lists and about substrings for strings.

Read it as is this value one of the items

Why: Which for [1, 2, 3] means: is it 1, or 2, or 3?

Test a nested list to see the difference

Why: [1, 2] in [[1, 2], 3] is True, because there the list IS an element.

This is the one place chapter 8's knowledge does not transfer verbatim, and it is worth meeting deliberately — the operator is the same and the question it asks is not.

12. Discriminate: does this work the same for lists as for strings?

Discrimination

Almost everything transfers. One thing does not.

Sort into buckets

For each operation, does it behave the same way on a list as on a string?

behaves the same
indexing with s[0]; negative indices; len; slicing with s[1:3]; an out-of-range index raising IndexError
behaves differently
the in operator
same
Every one of these carries over unchanged from chapter 8. Indices start at zero, negatives count from the end, len gives the count, slices are half-open, and out-of-range raises.
diff
in tests for a substring in a string and for an element in a list. 'ab' in 'abc' is True; [1,2] in [1,2,3] is False.

13. Complete it: make an empty list

Faded example

The simplest way to create one.

Fill in the blanks

results = []
print(len(results)) # should print 0

Why: Empty brackets create a list with no elements, and its length is 0. This is the standard starting point for the accumulator pattern from lesson 8b — you begin with an empty list and add to it, exactly as a string accumulator begins with an empty string. Note that [] is a list and '' is a string, and mixing them up gives a TypeError as soon as you try to append.

14. Think it through: why allow mixed types?

Socratic

A list can hold a string, a number and another list at once. Ask whether that is a good idea.

Discussion prompt

Some languages require every element of a list to be the same type. Name one advantage of Python's permissiveness and one thing it makes harder.

Hint: Think about what you can and cannot assume when reading a list.

Answer:

The advantage is expressiveness: a row of a table can be a list holding a name, an age and a score, without inventing a type to describe it. Chapter 12's sequences of sequences use this constantly.

What it makes harder is reasoning. You cannot assume that every element supports the same operations, so a loop that adds up a list will fail on the one element that happens to be a string.

The practical consequence is that a mixed list is usually a STRUCTURE — position 0 means one thing and position 1 another — while a uniform list is a COLLECTION. Chapter 12 introduces a separate type, the tuple, that is better suited to the first case.

15. Lists are mutable

Section

Section 2

16. The bracket operator on the left of an assignment

Concept

Unlike strings, lists are mutable. When the bracket operator appears on the left side of an assignment, it identifies the element of the list that will be assigned.

>>> numbers = [42, 123]
>>> numbers[1] = 5
>>> numbers
[42, 5]
PartWhat it doesNote
numbers[1]on the LEFT of the assignmentnames an element
= 5the element is replacedin the existing list
numbersthe same list objectwith a different element

The one-eth element of numbers, which used to be 123, is now 5. Compare this with lesson 8b, where exactly this syntax on a string raised a TypeError — the message there was that a str object does not support item assignment, and it was about the type rather than the syntax.

Think Python, 2nd edition — Allen B. Downey §10.1-10.5, pp. 90-90

17. Picture it: the object changed, not the name

Picture it

No new list was created. The existing one now holds something different.

Figure (svg): Two columns contrasting modifying a list element with rebuilding a string

The left column moves the arrow; the right column changes what the arrow points at.

That distinction — repointing a name versus changing an object — is the one lesson 10c is entirely about, and it is why the same syntax means such different things for the two types.

18. Worked example: the same line, two types

Worked example

One works and one raises. The difference is the type, not the syntax.

>>> s = 'abc'
>>> s[0] = 'x'
TypeError: 'str' object does not support item assignment
>>> t = ['a', 'b', 'c']
>>> t[0] = 'x'
>>> t
['x', 'b', 'c']
LineWhyResult
s[0] = 'x'strings are immutableTypeError
t[0] = 'x'lists are mutableworks
the differencethe type, not the syntaxsame line, two outcomes

Notice the syntax is identical.

Why: Both lines put the bracket operator on the left of an assignment. Nothing about the writing distinguishes them.

Notice the outcomes are not.

Why: The string raises a TypeError and the list quietly succeeds.

Read the error message again.

Why: It says a str object does not support item assignment — which was a hint, in lesson 8b, that some other type does.

Figure (svg): A panel showing the same assignment succeeding on a list and raising a TypeError on a string

The list assignment succeeds and the string assignment raises. This is the clearest possible demonstration that mutability is a property of the type rather than of the operation.

Verify: Check that no new list was created.

Why: The name t still refers to the same list object throughout — nothing was reassigned. Lesson 10c introduces the is operator, which makes that checkable rather than merely assertable, and it is the fact the whole aliasing section depends on.

19. Predict: does this raise?

Prediction

The syntax is identical for both types.

t = ['a', 'b', 'c']
t[1] = 'x'
print(t)
LineWhat happensResult
t[1] on the leftnames an elementlists are mutable
= 'x'replaces itin place
print(t)the same list, changed['a', 'x', 'c']

Predict first

What does this print?

  • ['a', 'b', 'c']
  • ['a', 'x', 'c']
  • A TypeError
  • ['x', 'b', 'c']

Correct: ['a', 'x', 'c'] — the element at index 1 was replaced, in the existing list.

Why: The identical line on a string raises a TypeError, because strings are immutable. Here it succeeds and modifies the list in place: no new list was created and t was never reassigned. That distinction between changing an object and repointing a name is what lesson 10c is about.

20. Worked example: what mutability lets you stop doing

Worked example

Chapter 8's technique becomes unnecessary. Compare the two.

# a string: rebuild from slices
s = 'abc'
s = s[:1] + 'x' + s[2:]

# a list: change the element
t = ['a', 'b', 'c']
t[1] = 'x'
VersionWhat it takesNote
the string versiontwo slices and a concatenationthree operations
the list versionone assignmentone operation
what changedthe list can be modifiedso nothing must be rebuilt

Recall the string technique.

Why: Lesson 8b's answer to change one character was to build a new string from the parts you wanted to keep, because nothing else was possible.

Compare the list version.

Why: One assignment. No slicing, no concatenation, no reassignment of the name.

Notice what this changes about your code.

Why: The accumulator pattern is still useful, and it is no longer forced. A list can be built by creating it and then filling it in.

Figure (svg): The state of the program after each line of Worked example what mutability lets you stop doing, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

One line instead of three, and no new object. Mutability removes the need for the rebuild-from-slices technique that immutability forced.

Verify: Ask what the string version costs for a long string.

Why: It copies every character that is not being changed, so modifying one character of a million-character string copies a million characters. A list assignment touches one element. That efficiency difference is real, and it is one reason the book suggests building text as a list and joining it at the end.

21. Trap: assuming everything you learned about strings still applies

Trap

The trap

A student who learned that strings cannot be changed, so operations return new ones applies the same reasoning to lists.

Generalise from the type you learned it on

Why: The rule was stated as a fact about sequences, and lists are sequences.

It was a fact about STRINGS. List methods mostly modify in place and return None, so t = t.sort() throws away the list and leaves t holding None — which lesson 10b covers in earnest.

The fix

Immutability is a property of the type, and only strings have it here.

Ask of every operation: does this modify, or does it produce something new?

Why: For lists both kinds exist, which is what makes the question necessary.

Check by looking at the original afterwards

Why: If it changed, the operation modified. If it did not, the operation returned something you needed to keep.

This is the single most common source of confusion in the chapter, and the book devotes a whole debugging section to it — including a list of four ways to append that are all wrong.

22. Compare: strings and lists

Comparison

Fill the blanks. Only one row differs.

Comparison matrix

OperationStringList
s[0]the first characterthe first element
len(s)the number of charactersthe number of elements
s[0] = xTypeErrorreplaces the element, in place
s[1:3]a new stringa new list

Three rows are the same idea with a different word. The third row is the difference the whole chapter turns on.

23. Complete it: replace an element

Faded example

The bracket operator, on the left.

Fill in the blanks

scores = [10, 20, 30]
scores[1] = 99 # should give [10, 99, 30]

Why: Index 1 is the second element, since indices start at zero. Putting the selection on the left of the assignment names the element to be replaced rather than producing its value — which is the same syntax that raises a TypeError on a string. Note that the list object is modified: no new list is created and scores is not reassigned.

24. Explain it yourself: what does *mutable* actually mean?

Explain it to yourself

State it in terms of objects and names.

Discussion prompt

Explain what it means for a list to be mutable, using the words object and name — and say why reassigning a variable is not an example of mutation.

Hint: One of them changes what the arrow points at; the other changes what is at the end of it.

Answer:

Mutable means the OBJECT can be changed: the same list, after the operation, holds something different from what it held before.

Reassigning a name is different — it moves the arrow to point at a different object, and leaves the original object exactly as it was. That works for immutable types too, which is why strings can be reassigned freely.

The distinction matters because two names can point at one object. Changing the object is visible through both names; moving one name is visible through neither. That is the whole of lesson 10c, and it only arises for mutable types.

25. Traversing a list, and updating one

Section

Section 3

26. Two loops, for two different jobs

Concept

The most common way to traverse the elements of a list is with a for loop, and the syntax is the same as for strings. That works well if you only need to READ the elements — but updating them needs the indices.

for cheese in cheeses:
    print(cheese)

for i in range(len(numbers)):
    numbers[i] = numbers[i] * 2
LoopWhat the variable holdsWhat it allows
for cheese in cheesesthe loop variable holds an elementread only
for i in range(len(numbers))the loop variable holds an indexread and write
numbers[i] = ...assignment through the indexmodifies the list

len returns the number of elements in the list, and range produces the indices from 0 to n minus one. Each time through the loop, i gets the index of the next element — and the assignment uses i both to read the old value and to write the new one.

Think Python, 2nd edition — Allen B. Downey §10.1-10.5, pp. 91-91

27. Picture it: the loop variable is a copy, the index is a handle

Picture it

Assigning to the loop variable changes nothing. Assigning through an index changes the list.

Figure (svg): Two columns contrasting a for loop over elements with a loop over indices, showing which can modify the list

The left loop cannot modify the list, however it is written.

This is why range(len(t)) exists as an idiom. It is the standard way to get at the positions when the elements alone are not enough.

28. Worked example: doubling every element

Worked example

The index is needed on both sides of the assignment.

numbers = [1, 2, 3]
for i in range(len(numbers)):
    numbers[i] = numbers[i] * 2
print(numbers)
PassWhat happensThe list after
i = 0numbers[0] = 1 * 2[2, 2, 3]
i = 1numbers[1] = 2 * 2[2, 4, 3]
i = 2numbers[2] = 3 * 2[2, 4, 6]

Get the indices with range and len.

Why: range returns the indices from 0 to n minus one, which are exactly the valid positions.

Read through the index.

Why: numbers[i] on the right of the assignment produces the current element.

Write through the same index.

Why: numbers[i] on the left names the element to be replaced. The assignment statement uses i to read the old value and to assign the new one.

Figure (svg): A list of three numbers shown before and after each element is doubled through its index

The same list object throughout, with each element replaced in turn.

[2, 4, 6]. The list is modified in place, one element at a time, and no new list is created.

Verify: Try it with a plain for loop and watch nothing happen.

Why: for x in numbers: x = x * 2 leaves the list unchanged, because x is a name holding a copy of the element and assigning to it just repoints x. That the two loops look similar and behave completely differently is exactly why the index version is worth knowing.

29. Predict: does this change the list?

Prediction

The assignment target is the loop variable.

t = [1, 2, 3]
for x in t:
    x = x * 2
print(t)
LineWhat happensResult
x = x * 2repoints the name xthe list is not mentioned
each passx is reassigned from the listthe previous value is discarded
print(t)unchanged[1, 2, 3]

Predict first

What does this print?

  • [2, 4, 6]
  • [1, 2, 3]
  • [1, 2, 3, 2, 4, 6]
  • An error

Correct: [1, 2, 3] — assigning to the loop variable repoints a name and leaves the list untouched.

Why: x is a name that holds each element in turn, and x = x * 2 makes x refer to a new number. The list is never mentioned on the left of any assignment, so nothing about it changes. The loop runs, does work, and has no effect — which is a silent failure with no error to indicate it. The index version is what modifies a list.

30. Worked example: the empty list runs zero times

Worked example

A property of the for loop, and it is exactly what you want.

for x in []:
    print('This never happens.')
AspectWhat is trueNote
the listhas no elementsnothing to iterate over
the bodynever runszero passes
no erroran empty sequence is legaltraversing it does nothing

Notice there is no special case.

Why: A for loop over an empty list never runs the body, without any check being needed.

Connect it to the while loop.

Why: This is the same property as lesson 7a's test-before-the-body rule, and lesson 8a's empty-string traversal, arriving a third time.

Notice what it saves.

Why: Code that processes a list needs no guard for the empty case, which is the commonest special case there is.

Figure (svg): The state of the program after each line of Worked example the empty list runs zero times, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

Nothing is printed and no error occurs. Traversing an empty sequence doing nothing is the correct behaviour, and it comes for free.

Verify: Check that range(len([])) behaves the same way.

Why: len is 0, so range produces no indices and the loop body never runs. Both traversal forms handle the empty case correctly without a guard, which is worth confirming because the index version involves arithmetic that could plausibly have gone wrong.

31. Trap: assigning to the loop variable

Trap

The trap

A student writes for x in t: x = x * 2, expecting the list to be doubled.

Read the loop variable as the element itself

Why: It holds the element, so assigning to it looks like changing the element.

It is a name holding a reference to the element, and assigning to it repoints that name. The list is untouched, and the loop runs to completion with no error and no effect.

The fix

To change the list you need the position, not the value.

Loop over range(len(t)) when you need to write

Why: Then t[i] on the left of an assignment names an element of the actual list.

Use the plain for loop when you only need to read

Why: It is shorter and cannot go out of range.

The symptom is characteristic: a loop that obviously does something and a list that is unchanged afterwards. If you see that, check whether the assignment target is a loop variable or an indexed element.

32. Discriminate: which loop do you need?

Discrimination

Ask whether the body writes to the list.

Sort into buckets

For each task, is a plain for loop enough, or do you need indices?

a plain for loop
print every element; count the elements greater than 10; build a new list of the squares
indices are needed
double every element in place; replace every negative element with zero; swap the first and last elements
plain
Each only reads the elements — printing, counting, or building a separate new list. None writes back into the original.
idx
Each modifies the original list, which requires naming positions. Assigning to a loop variable would repoint a name and change nothing.

33. Complete it: update every element

Faded example

You need the positions, not the values.

Fill in the blanks

for i in range(len(t)):
t[i] = t[i] + 1

Why: range(len(t)) produces the indices 0 to n-1, which are exactly the valid positions. Each pass reads t[i] and writes back to the same position, modifying the list in place. Note that range(len(t)) handles the empty list correctly with no guard — len is 0, range produces nothing, and the body never runs.

34. Think it through: why does the plain for loop not work for updating?

Socratic

State it in terms of names and objects.

Discussion prompt

Explain why for x in t: x = 0 leaves the list unchanged, using what you know about names referring to values.

Hint: What exactly does the assignment change?

Answer:

x is a name, and the assignment repoints it at a different object. That is lesson 2a's rule, unchanged: an assignment moves an arrow.

The arrow being moved is x's, and the list holds its own references to its elements. Moving x's arrow tells the list nothing.

To change what the list holds you have to assign to a position IN the list, which is what t[i] = 0 does. The bracket on the left is what makes the difference, and it is the same syntax that raises a TypeError on an immutable type.

35. List operations: concatenation and repetition

Section

Section 4

36. The same two operators, on a new type

Concept

The plus operator concatenates lists and the star operator repeats one a given number of times — exactly as they did for strings in lesson 2b.

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> a + b
[1, 2, 3, 4, 5, 6]
>>> [0] * 4
[0, 0, 0, 0]
>>> [1, 2, 3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
ExpressionWhat it doesResult
a + bjoins two lists end to enda NEW list
[0] * 4repeats a one-element listfour zeros
[1, 2, 3] * 3repeats a three-element listnine elements

Both operators produce a NEW list and leave the originals unchanged — which makes them the immutable-style operations on a mutable type, and that distinction becomes important in lesson 10c.

Think Python, 2nd edition — Allen B. Downey §10.1-10.5, pp. 91-91

37. Picture it: both operators create rather than modify

Picture it

This is worth noticing precisely because lists CAN be modified.

Figure (svg): Two columns contrasting operations that create a new list with those that modify an existing one

For strings only the left column exists. For lists both do, and telling them apart is the chapter's central skill.

The right-hand column is the subject of lesson 10b. The important thing here is that the operators are on the left — a + b does not change a.

38. Worked example: repetition builds a list of a known size

Worked example

The commonest use of the star operator, and it has a hazard.

>>> counts = [0] * 5
>>> counts
[0, 0, 0, 0, 0]
>>> counts[2] = 7
>>> counts
[0, 0, 7, 0, 0]
LineWhat happensNote
[0] * 5five copies of the number zeroa list of length 5
counts[2] = 7modify one positionthe others are unaffected
why safenumbers are immutableeach element is independent

Create a list of a known length.

Why: Repetition is how you make a list of a given size before you know what will go in it, which the counter patterns of chapter 13 use constantly.

Modify one position.

Why: The other elements are unaffected, so this behaves exactly as you would expect.

Notice why it is safe here.

Why: The repeated element is a number, which is immutable — so it does not matter that all five positions initially refer to the same zero.

Figure (svg): The state of the program after each line of Worked example repetition builds a list of a known size, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

A list of five zeros, of which one can be changed independently. Repetition is the standard way to create a list of a known size.

Verify: Ask what would happen if the repeated element were a list.

Why: [[0]] * 3 gives three references to the SAME inner list, so modifying one modifies all three. That is an aliasing bug and it is genuinely surprising — it is worth knowing the hazard exists, and lesson 10c explains exactly why it happens.

39. Predict: what does a hold afterwards?

Prediction

The plus operator creates rather than modifies.

a = [1, 2]
b = [3]
a + b
print(a)
LineWhat happensResult
a + bcreates a new list[1, 2, 3]
no assignmentthe new list is discardednothing kept
print(a)unchanged[1, 2]

Predict first

What does this print?

  • [1, 2, 3]
  • [1, 2]
  • [3]
  • None

Correct: [1, 2] — the concatenation created a new list which was immediately discarded, and a was never modified.

Why: This is the wasted-return-value mistake from lesson 6a, on a new type. The plus operator produces a new list and does not touch its operands, so a line consisting of a + b alone computes something and throws it away. Note that no error occurs, which is what makes it worth meeting deliberately.

40. Worked example: slice assignment

Worked example

A slice on the left of an assignment can replace several elements at once.

>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> t[1:3] = ['x', 'y']
>>> t
['a', 'x', 'y', 'd', 'e', 'f']
PartWhat it doesNote
t[1:3]on the LEFT of the assignmentnames two elements
= ['x', 'y']replaces themin place
the resultthe same list, two elements changedno new list

Recognise the slice, from lesson 8b.

Why: t[1:3] selects the elements at indices 1 and 2, half-open as always.

Notice it is on the left.

Why: A slice operator on the left side of an assignment can update multiple elements — which for a string was impossible.

Check what changed.

Why: Two elements replaced, in the existing list. Nothing was created and no name was reassigned.

Figure (svg): A six-element list with the two elements at indices one and two highlighted as the ones being replaced

Two positions replaced; the rest of the list is untouched.

['a', 'x', 'y', 'd', 'e', 'f']. Slice assignment replaces a run of elements in place, which is the multi-element version of t[i] = x.

Verify: Try replacing a slice with a different number of elements.

Why: t[1:3] = ['x'] leaves a shorter list, and t[1:3] = ['x','y','z'] a longer one — the replacement need not match the slice's length. That flexibility is genuinely useful and worth discovering deliberately, since it means slice assignment can insert and delete as well as replace.

41. Trap: expecting + to modify the left operand

Trap

The trap

A student writes t + [x] on a line by itself, expecting it to add an element to t.

Assume that because lists are mutable, operations on them modify

Why: The type can be modified, so an operation on it plausibly does.

The plus operator creates a new list and returns it. On a line by itself the result is discarded and t is unchanged — a legal statement with no effect at all.

The fix

Operators create; methods mostly modify.

If you use +, assign the result

Why: t = t + [x], which repoints t at the new list.

If you want to modify in place, use append

Why: t.append(x), which changes the existing list and returns None.

The book lists exactly this among four wrong ways to add an element, and notes that only one of them causes a runtime error — the other three are legal and do the wrong thing. Lesson 10b works through all four.

42. Sort: does this create a new list or modify an existing one?

Sorting

Both kinds exist for lists, which is what makes the question necessary.

Sort into buckets

For each operation on a list t, does it create or modify?

creates a new list
t + [x]; t * 2; t[1:3]; t[:]
modifies t in place
t[0] = x; t[1:3] = [x, y]
new
Each is an expression producing a value: concatenation, repetition, and slices all build a new list and leave t alone. A result you do not assign is discarded.
mod
Both put a selection on the LEFT of an assignment, which names part of the existing list and replaces it. Nothing new is created and no name is reassigned.

43. Complete it: make a list of ten zeros

Faded example

The repetition operator, on a one-element list.

Fill in the blanks

counts = [0] ***** 10
print(len(counts)) # should print 10

Why: Repetition takes a list and an integer and produces a new list with the elements repeated. Note the shape of the left operand: [0] * 10 gives ten zeros, while 0 * 10 is just the number 0. Repeating a list containing a MUTABLE element is a different matter — [[0]] * 3 gives three references to one inner list, which is an aliasing hazard lesson 10c explains.

44. Explain it: why does t[:] matter for lists?

Explain it

For strings it was a curiosity. Here it has a purpose.

Discussion prompt

For a string, s[:] gives back the same string and seems pointless. For a list it is genuinely useful. Explain why, without using the word aliasing.

Hint: What can you do to a list that you cannot do to a string?

Answer:

Say: a list can be modified, so sometimes you want a separate copy that you can change without affecting the original. t[:] gives you one.

For a string it makes no difference, because nothing can change either copy — which is why it looked pointless there.

The book puts it directly: since lists are mutable, it is often useful to make a copy before performing operations that modify lists. That is why the same expression is a curiosity for one type and a technique for the other.

45. Putting it together: reading and writing list code

Section

Section 5

46. Two questions to ask about any list operation

Concept

Everything in this lesson comes down to two questions: does this operation read or write, and does it create a new list or modify an existing one? The answers determine which loop you need and whether the result must be assigned.

# reads only, creates nothing:
for x in t:
    print(x)

# writes into the existing list:
for i in range(len(t)):
    t[i] = t[i] * 2

# creates a new list, leaves t alone:
doubled = [x * 2 for x in t]   # chapter 19; for now, a loop
IntentWhich formEffect on t
read onlya plain for loopthe simplest form
write in placeindices, and assignment to t[i]modifies t
build a new onean accumulatort is unchanged

The third form is the accumulator pattern from lesson 8b, which is still available and no longer compulsory. Choosing between the second and third is a real decision, and lesson 10c gives the reason it matters.

Think Python, 2nd edition — Allen B. Downey §10.1-10.5, pp. 90-91

47. Picture it: three shapes for three intents

Picture it

The intent decides the shape, and the shape decides what the caller sees.

Figure (svg): A three-stage diagram showing read-only, modify-in-place and build-new as three distinct loop shapes

The middle one changes t; the others do not.

Getting this choice wrong is the commonest bug in the chapter, and its symptom is always the same: a loop that clearly does something and a list that is unchanged.

48. Worked example: the same task, two ways

Worked example

Modify in place, or build a new list. Both are correct and they differ.

# in place
def double_all(t):
    for i in range(len(t)):
        t[i] = t[i] * 2

# building a new list
def doubled(t):
    res = []
    for x in t:
        res.append(x * 2)
    return res
FunctionWhat it doesNote
double_allmodifies the caller's listreturns None
doubledleaves the caller's list alonereturns a new list
the choicewho should see the change?a design decision

Write the in-place version.

Why: It needs indices, and it returns nothing — its whole effect is on the list it was given.

Write the new-list version.

Why: It uses the accumulator pattern from lesson 8b, reads with a plain for loop, and returns a result.

Notice that both are correct.

Why: They are different functions with different contracts, and neither is a broken version of the other.

Figure (svg): Two columns comparing an in-place function with one that returns a new list, on what each does and how each is misused

Two contracts, two characteristic mistakes, and they are exact opposites.

Two functions, one modifying and one returning. The choice is about who should see the change, and it must be documented — a caller cannot tell from the name.

Verify: Check what each returns.

Why: double_all returns None, because it has no return statement — so t = double_all(t) would destroy the list. doubled returns the new list, so doubled(t) on its own would discard it. Each has a characteristic misuse, and they are opposites.

49. Predict: what does the caller see?

Prediction

The function modifies through an index.

def zero_first(t):
    t[0] = 0

nums = [7, 8, 9]
zero_first(nums)
print(nums)
LineWhat happensResult
t[0] = 0assigns to a positionin the list itself
the caller's listthe same objectshared
print(nums)changed[0, 8, 9]

Predict first

What does this print?

  • [7, 8, 9]
  • [0, 8, 9]
  • None
  • An error

Correct: [0, 8, 9] — the function modified the list, and the caller sees the change.

Why: The parameter t and the variable nums refer to the same list object, so an assignment to t[0] changes what nums refers to as well. This is genuinely new: no function so far could affect a caller's variable, because every earlier type was immutable. Lesson 10c makes the mechanism explicit with a stack diagram.

50. Worked example: diagnosing a loop that does nothing

Worked example

The symptom is specific enough to name the cause.

def double_all(t):
    for x in t:
        x = x * 2

nums = [1, 2, 3]
double_all(nums)
print(nums)
PartWhat happensResult
the loopruns three timesdoes arithmetic
x = x * 2repoints a local namethe list is not mentioned
print(nums)unchanged[1, 2, 3]

Confirm the loop runs.

Why: It does, three times, and computes three products. Nothing is wrong with the arithmetic.

Find what the assignment targets.

Why: x, which is a local name. Assigning to it repoints that name and tells the list nothing.

State the fix.

Why: Loop over range(len(t)) and assign to t[i], so that the assignment names a position in the actual list.

Figure (svg): The state of the program after each line of Worked example diagnosing a loop that does nothing, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

The list is unchanged. The assignment repoints a loop variable rather than replacing an element, which is a silent failure with no error at all.

Verify: Add a print inside the loop and confirm the work is happening.

Why: Printing x after the multiplication shows 2, 4 and 6, so the computation is correct and only the storing is wrong. Separating is the work happening from is it being kept is exactly lesson 6c's three-possibilities method, applied here.

51. Trap: a function that modifies when the caller expected a copy

Trap

The trap

A function sorts the list it is given, and the caller is surprised that their original list has been reordered.

Modify in place because it is convenient

Why: It avoids building a copy and the function is shorter.

The caller's list changed without the call site saying so. sort_it(t) looks like it produces something, and instead it silently reorders data the caller may still need.

The fix

Decide deliberately, and make the choice visible.

Name in-place functions with a verb that implies change

Why: sort_in_place, or the convention of returning None so that misuse fails.

Otherwise build and return a new list

Why: Which leaves the caller's data alone and makes the function usable in an expression.

This is lesson 4b's contract again: modifying an argument is a postcondition the caller has to know about, and it belongs in the docstring. Python's own library makes the same distinction — sort modifies and sorted returns a new list.

52. Error analysis: three attempts to double a list

Error analysis

One works. Mark what is wrong with the other two.

Annotate

  • The first assigns to the loop variable, which repoints a local name and leaves the list untouched. It runs, does arithmetic, and has no effect.
  • That failure is silent: no error, no message, and a loop that visibly does work.
  • The second is correct. It uses indices, so t[i] on the left names a position in the actual list, and the elements are replaced in place.
  • The third does something completely different: repetition, not doubling of values. t * 2 gives a list twice as long with the same elements repeated.
  • It is also discarded, because the result is not assigned — so even its wrong answer goes nowhere.
  • Three plausible-looking lines, one correct, and neither failure raises an error. That is why the two questions — read or write, create or modify — have to be asked deliberately.

The third is a reminder that the star operator repeats a list rather than scaling its elements, which is the same distinction as for strings.

53. Compare: three ways to process a list

Comparison

Fill the blanks. The intent decides the form.

Comparison matrix

IntentLoop formEffect on the original
read each elementfor x in tunchanged
update each elementfor i in range(len(t))modified in place
build a related listfor x in t, appending to a new listunchanged

Two of the three leave the original alone, and only one of those produces anything — which is why the third form must return its result.

54. Where modify-versus-copy matters

Real world

The decision is not specific to Python.

Discussion prompt

Think of a tool or service that either edits something in place or produces a new version — a document editor, an image filter, a file operation. What are the consequences of each choice, and when is in place the wrong default?

Hint: Consider whether the original is still needed.

Answer:

Editing in place is efficient and destructive: the original is gone. Producing a new version costs space and keeps both, which is why every serious editor has undo.

In place is the wrong default whenever the original might still be wanted, or whenever somebody else might be looking at it — which for a shared list is exactly the case.

Python's library makes the choice explicit in its naming: sort modifies and sorted returns a new list, list.reverse modifies and reversed produces a new sequence. Following that convention in your own functions is the clearest way to make the contract visible.

55. Compare: strings and lists, side by side

Comparison

Fill the blanks. The last row is the difference everything else follows from.

Comparison matrix

PropertyStringList
what the elements arecharactersvalues of any type
indexing and slicingfrom 0, half-open slicesidentical
what in testsa substringmembership of a single element
can it be modified?no — immutableyes — mutable

Two rows the same, two different. The third catches people once; the fourth changes how you write everything.

56. The procedure: choosing how to process a list

Pattern

Five steps, and the first two decide the shape before any code is written.

  1. Ask whether the body needs to WRITE to the list, or only to read it.
  2. If only reading, use a plain for loop over the elements — it is shorter and cannot go out of range.
  3. If writing, loop over range(len(t)) and assign to t[i], because assigning to a loop variable changes nothing.
  4. If the result should be a separate list, use the accumulator pattern and leave the original alone.
  5. Decide whether the caller should see the change, and make that visible — by returning a new list, or by naming the function so that the modification is expected.

Step 3 is the one whose failure is silent. A loop that assigns to its loop variable runs correctly, does the arithmetic, and has no effect at all.

Python documentation — An Informal Introduction to Python An Informal Introduction to Python

57. Check yourself 1 of 3: mutability

Check

The same line, on two types.

t = ['a', 'b', 'c']
t[0] = 'x'
PartWhat happensNote
t[0] on the leftnames an elementlists are mutable
the assignmentreplaces it in placeno new list
on a stringthe same line raisesTypeError

Check your understanding

Why does t[0] = 'x' work for a list but not for a string?

  • A. Because lists use different bracket syntax
  • B. Because lists are mutable and strings are not (correct)
  • C. Because 'x' is a string and t contains strings
  • D. Because strings do not support indexing

Answer: B

Why: The syntax is identical; the difference is entirely in the type. A list can be modified after it is created, so an element can be replaced in place. A string cannot, which is why the same line raises a TypeError saying that a str object does not support item assignment.

Why A tempts people
The syntax is exactly the same — that is what makes this worth stating. Nothing in the line distinguishes the two cases.
Why C tempts people
The type of the value being assigned is irrelevant. Assigning a number to a list element works equally well.
Why D tempts people
Strings support reading by index perfectly well. It is only writing that they refuse.

58. Check yourself 2 of 3: updating in a loop

Check

The assignment target decides whether anything changes.

t = [1, 2, 3]
for x in t:
    x = 0
print(t)
LineWhat happensResult
x = 0repoints the loop variablea local name
the listnever appears on the leftuntouched
print(t)unchanged[1, 2, 3]

Check your understanding

What does this print?

  • A. [0, 0, 0]
  • B. [1, 2, 3] (correct)
  • C. 0
  • D. An error

Answer: B

Why: Assigning to the loop variable repoints a local name and leaves the list untouched — the list never appears on the left of any assignment. To modify the list you need the positions: for i in range(len(t)) with t[i] = 0. The failure is silent, which is what makes it worth meeting deliberately.

Why A tempts people
This would require the assignment to reach into the list, which needs an indexed target rather than the loop variable.
Why C tempts people
The loop variable does end up holding 0, and printing the list shows the list rather than the variable.
Why D tempts people
Nothing here is illegal. The loop runs, the assignments happen, and none of them affects the list.

59. Check yourself 3 of 3: create or modify

Check

Operators create; indexed assignment modifies.

Check your understanding

Which of these modifies the existing list t rather than creating a new one?

  • A. t + [4]
  • B. t * 2
  • C. t[1:3] = ['x', 'y'] (correct)
  • D. t[1:3]

Answer: C

Why: A slice on the LEFT of an assignment names part of the existing list and replaces it in place. The other three are expressions that produce new lists and leave t alone — and a result that is not assigned is simply discarded.

Why A tempts people
Concatenation produces a new list. On a line by itself the result is thrown away and t is unchanged.
Why B tempts people
Repetition also produces a new list, twice as long, and leaves t alone.
Why D tempts people
A slice on the RIGHT of an assignment — or on its own — produces a new list containing copies of the references. Only its position relative to the equals sign makes the difference.

60. Where this shows up outside this course

Real world

Editing in place versus producing a new version is a distinction with everyday consequences.

Discussion prompt

Think of two tools that do the same job, one editing a file in place and one writing a new file. What does each get right, and which mistakes does each make possible?

Hint: Consider what happens when something goes wrong halfway through.

Answer:

In-place editing is fast and needs no extra space, and it destroys the original — so a failure halfway through leaves you with neither the old version nor a complete new one.

Writing a new file is safe and slower, and it leaves you to decide what to do with two versions.

The programming version is exactly the same trade, and Python's library makes the choice visible in its names: sort modifies and sorted returns a new list. Adopting that convention in your own functions is the clearest way to tell a caller which kind they are getting.

61. Confidence wager: commit before you check

Commit first

Answer, then rate your confidence. This is the chapter's most common silent bug.

Predict first

A function contains for x in t: x = x * 2. What happens to the caller's list?

  • Every element is doubled
  • Nothing — the list is unchanged
  • A TypeError, because the loop variable cannot be assigned
  • The list is replaced by a new one

Correct: Nothing — the list is unchanged, because assigning to the loop variable repoints a local name rather than replacing an element.

Why: x is a name that holds each element in turn, and x = x * 2 makes x refer to a different number. The list is never mentioned on the left of any assignment, so nothing about it changes. The loop runs, performs the arithmetic correctly, and has no effect — with no error to indicate it. The fix is to loop over range(len(t)) so that t[i] on the left names a position in the actual list. This failure is worth over-learning because its symptom is so distinctive: a loop that obviously does work, and data that is unchanged afterwards.

62. Explain it to someone else

Explain it

The mutability distinction is what a student coming from chapter 8 most needs.

Discussion prompt

A classmate has learned that string operations always return new strings and never modify. Explain what changes for lists, and give them one line that behaves differently on the two types.

Hint: The line is the same on both types.

Answer:

Say: strings are immutable, so every operation on one produces a new string. Lists are mutable, so some operations modify the list in place — and both kinds exist, which is what makes it necessary to know which is which.

The line: t[0] = 'x'. On a list it replaces the first element; on a string it raises a TypeError saying that a str object does not support item assignment.

Then give them the question to ask about every list operation from now on: does this create something new, or change what I already have? For strings the answer was always the first; for lists it has to be checked.

63. Exit ticket

Exit ticket

One honest answer. It decides what the next lesson opens with.

Predict first

Which of these is still least solid for you?

  • Making lists, including nested and empty ones
  • Mutability: putting the bracket operator on the left of an assignment
  • Choosing between a plain for loop and range(len(t))
  • Telling operations that create a new list from those that modify one

Correct: Whichever you picked is the right answer — this one is for you, not for a mark.

Why: Creating lists is syntax and settles immediately, except for the nesting rule about length. Mutability is a single fact with very large consequences, and it is worth stating out loud until it is automatic. The loop choice is where the silent bug lives — a loop that assigns to its loop variable is the commonest mistake in this chapter. And the create-versus-modify distinction is the one that keeps mattering: the next lesson opens with four wrong ways to add an element to a list, three of which produce no error at all.

64. Synthesis: draw the map of this lesson

Connect it up

One page, from memory.

Draw it

Draw a list of four elements as a box with the word list beside it and the indices marked, with a name pointing at it. Then, in two columns, list every operation from this lesson that CREATES a new list and every one that MODIFIES the existing one. Finally, write the two loop forms side by side and mark which of them can change the list, with one sentence saying why the other cannot.

65. What you can do now

Recap

Three pages, and a sequence that can be changed.

If you remember one thingIt is this
From lists as sequencesEverything from chapter 8 transfers, except what in tests.
From mutabilityThe bracket operator on the left is the difference between the two types.
From traversalAssigning to a loop variable changes nothing. Use indices to write.
From the operatorsPlus and star create new lists. They do not modify their operands.
From nestingA nested list is one element, whatever it contains.

The next lesson meets the list methods — append, extend, sort — and the fact that most of them modify the list and return None, which is the source of the single most common mistake in the chapter.

Think Python, 2nd edition — Allen B. Downey §10.1-10.5, pp. 89-91 — everything on these slides traces back here

Sources

  1. Think Python, 2nd edition — Allen B. Downey — Allen B. Downey, Think Python: How to Think Like a Computer Scientist, 2nd edition (Green Tea Press, 2015), §10.1-10.5, pp. 89-91
  2. Python documentation — An Informal Introduction to Python
  3. Python documentation — Data Structures

Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108