10b List Methods, Map, Filter, Reduce, and Deleting Elements

This lesson introduces the list methods and the fact that most of them return None, names the three patterns that most list processing is built from, gives four ways to delete an element, and converts between lists and strings with list, split and join.

Subject: Python · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Lesson 10b List Methods, Map, Filter, Reduce, and Deleting Elements

Title

Python · Chapter 10 — Lists

§10.6-10.9, pp. 92-94

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.6-10.9, pp. 92-94 — the pages these objectives are drawn from

3. Before we start: how would you add an element?

Warm-up

You have two tools already. Try both and notice what each leaves behind.

Discussion prompt

Using only the previous lesson, write two different ways to add the number 4 to the end of a list t. What is different about what each one leaves you with?

Hint: One of them uses an operator.

Answer:

t = t + [4] builds a new list and repoints t at it. The original list still exists, unchanged, and t no longer refers to it.

There is no second way yet — the previous lesson had no method for adding an element. This lesson supplies one, and it works quite differently.

append modifies the existing list rather than creating a new one, and the difference between those two matters enormously once more than one name refers to the same list. That is lesson 10c.

4. The one idea behind this lesson: most list methods return None

Concept

Python provides methods that operate on lists, and most of them are void: they modify the list and return None. If you accidentally write t = t.sort(), you will be disappointed with the result.

void method — A method that modifies the object it is called on and returns None.

This is the exact opposite of string methods, which never modify and always return a new string. The two types behave in opposite ways, and applying either habit to the other type produces a bug with no error message.

Figure (svg): Two columns contrasting string methods with list methods on what they modify and what they return

The correct usage for one type is the mistake for the other.

Think Python, 2nd edition — Allen B. Downey §10.6-10.9, pp. 92-92

5. append, extend, sort — and what they return

Section

Section 1

6. Three methods, all of them void

Concept

append adds a new element to the end of a list, extend appends all the elements of another list, and sort arranges the elements from low to high. All three modify the list and return None.

>>> t = ['a', 'b', 'c']
>>> t.append('d')
>>> t
['a', 'b', 'c', 'd']
>>> t1 = ['a', 'b', 'c']
>>> t2 = ['d', 'e']
>>> t1.extend(t2)
>>> t1
['a', 'b', 'c', 'd', 'e']
CallWhat it doesNote
append('d')adds ONE elementthe list grows by one
extend(t2)adds all of t2's elementsthe list grows by len(t2)
t2 afterwardsunmodifiedextend reads it and does not change it

Notice that neither call was assigned to anything. That is correct: the method modifies the list, so there is nothing to keep. Assigning the result would store None.

Think Python, 2nd edition — Allen B. Downey §10.6-10.9, pp. 92-92

7. Picture it: append adds one, extend adds several

Picture it

The difference is whether the argument is added as an element or unpacked.

Figure (svg): Two columns contrasting append adding its argument as one element with extend adding each of its argument's elements

Same argument, two very different results.

Using append where extend was meant gives a nested list — which the book lists among the four wrong ways to add an element, because it is legal and does the wrong thing.

8. Worked example: the t = t.sort() disaster

Worked example

The single most common mistake in the chapter. Cause it deliberately.

>>> t = ['d', 'c', 'a']
>>> t = t.sort()
>>> t
None
>>> len(t)
TypeError: object of type 'NoneType' has no len()
StepWhat happensConsequence
t.sort()sorts the list in placethe list is now sorted
it returns Nonebecause it is a void methodthere is no result
t = Nonethe name is repointed at Nonethe sorted list is lost

Notice that the sort itself worked.

Why: The list really was sorted, in place, before anything else happened.

Notice what the method returned.

Why: None, because most list methods are void: they modify the list and return None.

See what the assignment did.

Why: It repointed t at None, so the name no longer refers to the sorted list. The list still exists and nothing refers to it.

Figure (svg): A state diagram showing t repointed at None while the sorted list remains with nothing referring to it

The list was sorted and then abandoned. The assignment moved the only arrow pointing at it.

t holds None and the sorted list is unreachable. Because sort returns None, the next operation you perform with t is likely to fail — which is at least loud.

Verify: Keep a second reference and confirm the list really was sorted.

Why: With u = t before the mistaken assignment, u still refers to the sorted list and shows ['a','c','d']. The sorting worked perfectly; only the assignment was wrong, and separating those two facts is what makes the diagnosis clear.

9. Predict: what does t hold?

Prediction

The method modifies and returns None.

t = [3, 1, 2]
t = t.sort()
print(t)
LineWhat happensResult
t.sort()sorts in place, returns Nonethe list is sorted
t = Nonethe name is repointedthe list is lost
print(t)Nonenot the sorted list

Predict first

What does this print?

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

Correct: None — sort modifies the list in place and returns None, which the assignment then stored in t.

Why: The sorting worked. The assignment is what went wrong: it repointed t at the method's return value, which is None, and the sorted list became unreachable. The correct usage is t.sort() with no assignment, or t = sorted(t) using the function that returns a new list.

10. Worked example: the four wrong ways to append

Worked example

The book lists these deliberately. Only one of them raises an error.

# correct:
t.append(x)
t = t + [x]
t += [x]

# wrong:
t.append([x])   # adds a nested list
t = t.append(x) # t becomes None
t + [x]         # result discarded
t = t + x       # TypeError
LineWhat happensNote
t.append([x])adds one element, a listlegal, wrong shape
t = t.append(x)append returns Nonelegal, t is destroyed
t + [x]creates a list and discards itlegal, no effect
t = t + xcannot concatenate a list and a non-listTypeError

Notice how many are legal.

Why: Three of the four wrong versions run without any error at all. Only the last one causes a runtime error.

Identify each failure.

Why: One adds a nested list, one replaces t with None, and one computes a new list and throws it away.

Draw the conclusion.

Why: Legality is not correctness. Three of these would survive a test that only checked for errors.

Figure (svg): The state of the program after each line of Worked example the four wrong ways to append, drawn as a ladder with one rung per traced line

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

Three wrong versions run silently and one raises. The book's own advice follows: try each in interactive mode to make sure you understand what they do.

Verify: Check the length after each of the three legal wrong versions.

Why: The nested-append gives a list one longer with a list inside it, the None assignment gives a TypeError on the next len, and the discarded concatenation leaves the length unchanged. Three different symptoms for three different mistakes, and none of them is an error at the point of the mistake.

11. Trap: applying string habits to list methods

Trap

The trap

A student who learned s = s.upper() writes t = t.sort() by analogy.

Generalise the assignment habit across types

Why: It was correct and necessary for every string method, so it looks like the rule for methods.

It was a rule about immutable types. List methods mostly modify in place and return None, so the assignment stores None and loses the list.

The fix

Ask of each method: does it modify, or does it return?

For a method that modifies, call it and do not assign

Why: t.sort() on its own is the correct usage.

For a function that returns, assign the result

Why: sorted(t) returns a new sorted list and leaves t alone — which is the function form of the same operation.

Python's library makes the distinction visible where it can: sort modifies and sorted returns, reverse modifies and reversed returns. Where two names exist, the shorter verb usually modifies.

12. Discriminate: append or extend?

Discrimination

Ask whether the argument should become one element or several.

Sort into buckets

For each intention, which method is right?

append
add the number 5 to the end; add a sublist as a single element; add one string to a list of strings
extend
add all the elements of another list; merge two lists into the first; add each character of a string as a separate element
app
Each adds exactly one element to the list, whatever that element happens to be — including a list, when the nesting is what you want.
ext
Each adds the elements of a sequence individually. Note that extending with a string adds its characters one at a time, which is occasionally what you want and often a surprise.

13. Sort: correct or wrong way to add an element?

Sorting

The book gives three of each. Three of the wrong ones are legal.

Sort into buckets

For a list t and an element x, sort each line.

correct
t.append(x); t = t + [x]; t += [x]
wrong
t = t.append(x); t.append([x]); t + [x]
ok
The book lists exactly these three as correct: the method call, the concatenation assigned back, and the augmented assignment. All three end with t referring to a list one element longer.
no
One stores None, one adds a nested list, and one computes a new list and discards it. All three are legal — none raises an error — which is why they survive careless testing.

14. Explain it yourself: why do list methods return None?

Explain it to yourself

It is a deliberate design decision. Argue for it.

Discussion prompt

Python's list methods could have returned the modified list, allowing t = t.sort() to work. Suggest why returning None is arguably better.

Hint: Consider what returning the list would suggest about what happened.

Answer:

Returning the list would suggest that a new list was produced — the shape of the call would look identical to sorted(t), which really does return a new list.

Returning None makes the difference visible: a method that gives you nothing back has clearly done its work somewhere else, namely in place.

It also fails loudly. t = t.sort() leaves t holding None, and the next operation raises — whereas if sort returned the list, code that modified a shared list while believing it had a private copy would fail silently and much later. Given that only one of the two can be loud, this is the better choice.

15. Reduce: accumulating to a single value

Section

Section 2

16. A pattern you have already been writing

Concept

To add up all the numbers in a list you use an accumulator: a variable initialized before the loop and updated on every pass. An operation that combines a sequence of elements into a single value is sometimes called a reduce.

accumulator — A variable used in a loop to add up or accumulate a result.

def add_all(t):
    total = 0
    for x in t:
        total += x
    return total
LineIts roleNote
total = 0the accumulator, initializedbefore the loop
total += xaugmented assignmentsame as total = total + x
return totalafter the loopone value from many

The += operator provides a short way to update a variable: total += x is equivalent to total = total + x. Adding up a list is common enough that Python provides it as the built-in function sum.

Think Python, 2nd edition — Allen B. Downey §10.6-10.9, pp. 93-93

17. Picture it: many values in, one value out

Picture it

That shape is what makes it a reduce.

Figure (svg): A diagram showing a list of several elements feeding an accumulator loop and producing a single value

The defining property: the output is a single value, whatever the input length.

You have written this before — the counter in lesson 8b and the string accumulator in lesson 8b are both reduces, one producing a number and one a string.

18. Worked example: the augmented assignment

Worked example

A shorthand you will see constantly. Confirm it means what it says.

>>> total = 0
>>> total += 5
>>> total
5
>>> total = total + 5
>>> total
10
LineWhat happensResult
total += 5read total, add 5, store back5
total = total + 5exactly the same thing10
equivalencethe two forms are interchangeablefor numbers

Read the shorthand.

Why: total += x is equivalent to total = total + x — read the old value, combine, and store back.

Notice it is an update.

Why: Lesson 7a's rule applies: the variable must be initialized first, or the read fails with a NameError.

Note the other forms.

Why: The same shorthand exists for the other operators: -=, *=, /= and so on, each meaning the same read-combine-store.

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

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

The two forms are equivalent for numbers. += is shorter and says update more directly, which is why accumulator loops are usually written with it.

Verify: Try += without initializing first.

Why: It raises a NameError, exactly as total = total + x would, because both read the old value before assigning. The shorthand changes nothing about the semantics — which is worth confirming, since a shorter form can look like it does something different.

19. Predict: what does this return?

Prediction

The accumulator is initialized to zero.

def product(t):
    result = 0
    for x in t:
        result *= x
    return result
StepWhat happensValue
result = 0the wrong identity for multiplication0
0 * anythingstill zero0
every passleaves it at zero0

Predict first

What does product([2, 3, 4]) return?

  • 24
  • 0
  • 9
  • An error

Correct: 0 — multiplying the initial zero by anything leaves zero, so the answer is always zero whatever the list.

Why: The loop runs correctly and every multiplication is performed; the initialization destroys the result. The correct initial value for a multiplicative accumulator is 1, because one times anything is unchanged. The general rule is that the accumulator starts at the identity element for the operation — which is also the answer a reduce over an empty list should give.

20. Worked example: sum, and when to write your own

Worked example

Python provides the commonest reduce. The others you write yourself.

>>> t = [1, 2, 3]
>>> sum(t)
6

# a reduce Python does not provide:
def product(t):
    result = 1
    for x in t:
        result *= x
    return result
OperationWhere it comes fromNote
sum(t)the built-in reduce for addition6
productthe same pattern with multiplicationwritten by hand
result = 1the identity for multiplicationnot 0

Use the built-in where one exists.

Why: Adding up the elements of a list is such a common operation that Python provides it as sum.

Write your own where none does.

Why: The pattern is identical — initialize, loop, combine, return — with a different operator.

Notice the initialization changes.

Why: For addition the accumulator starts at 0; for multiplication it starts at 1, because that is the value that leaves the result unchanged.

Figure (svg): A ladder showing an accumulator growing from zero through partial sums to the total

One value at the end, whatever the length of the list.

sum for addition, and the same pattern by hand for anything else. The only thing that varies is the operator and the starting value that goes with it.

Verify: Check both on the empty list.

Why: sum([]) is 0 and product([]) is 1 — each returns its accumulator's initial value. That those are the right answers is not a coincidence: the identity element is exactly the value that a reduce over nothing should produce, which is why it is the right initialization.

21. Trap: initializing the accumulator wrongly

Trap

The trap

A student writes a product function and initializes the accumulator to 0.

Copy the initialization from the summing version

Why: The pattern is the same, so the starting value looks like part of the boilerplate.

Every product comes out as 0, because multiplying by the initial zero destroys everything. The loop runs correctly and the answer is always the same.

The fix

The initial value is the one that leaves the result unchanged.

For addition, zero; for multiplication, one

Why: Adding zero and multiplying by one both change nothing, which is what an empty accumulator should do.

Check it against the empty list

Why: A reduce over no elements should return the initialization, and that answer should be sensible.

For a string accumulator the same reasoning gives the empty string, and for a list accumulator the empty list. In every case the question is: what value combined with anything leaves it alone?

22. Complete it: count the elements over ten

Faded example

A reduce that counts rather than sums.

Fill in the blanks

def count_big(t):
n = 0
for x in t:
if x > 10:
n += 1
return n

Why: A counting accumulator starts at zero, because zero is the right answer for a list with no big elements — including the empty list. Note the shape: this is the counter pattern from lesson 8b, now recognised as a kind of reduce. What makes it a reduce is that many values go in and one comes out.

23. Discriminate: is this a reduce?

Discrimination

Ask what shape the output has.

Sort into buckets

For each operation on a list, is it a reduce?

a reduce: many in, one out
add up all the elements; count how many are negative; find the largest element; join the elements into one string
not a reduce
double every element; keep only the positive elements
red
Each produces exactly one value from the whole list, whatever its length — a sum, a count, a maximum, or a single joined string.
not
Each produces another sequence. Doubling every element is a map and selecting the positive ones is a filter, and both are covered in the next section.

24. Think it through: why is the identity element the right initialization?

Socratic

It is not a coincidence that it also answers the empty case.

Discussion prompt

For a summing accumulator the initial value is 0 and for a product it is 1. Explain why the identity element is the right choice, using the empty list as your argument.

Hint: What should a reduce over nothing produce?

Answer:

A reduce over an empty list returns its initialization untouched, so the initialization IS the answer for the empty case — and the sensible answer there is the identity.

It also has to be the identity for non-empty lists, because the first pass combines it with the first element and must leave that element unchanged.

So one requirement settles both cases at once, which is a good sign that it is the right way to think about it rather than two separate rules to remember.

25. Map and filter: building a new list

Section

Section 3

26. Transforming every element, or selecting some

Concept

Sometimes you want to traverse one list while building another. An operation that applies something to every element is called a map; one that selects some of the elements is called a filter.

def capitalize_all(t):
    res = []
    for s in t:
        res.append(s.capitalize())
    return res

def only_upper(t):
    res = []
    for s in t:
        if s.isupper():
            res.append(s)
    return res
FunctionRelationship of input to outputWhich pattern
capitalize_allone output per inputa map
only_uppersome inputs produce outputa filter
bothres is an accumulator holding a listthe same shape

res is initialized with an empty list, and each time through the loop the next element is appended — so res is another kind of accumulator. Most common list operations can be expressed as a combination of map, filter and reduce.

Think Python, 2nd edition — Allen B. Downey §10.6-10.9, pp. 93-94

27. Picture it: three shapes, distinguished by their output

Picture it

The length of the result is what tells them apart.

Figure (svg): Two columns comparing map, filter and reduce by the length of what each produces

Same input, three shapes of result.

Naming them matters because most list processing is a combination of the three, and because chapter 19 provides a compact syntax for map and filter.

28. Worked example: a map preserves the length

Worked example

One output element per input element, always.

>>> t = ['alice', 'bob', 'carol']
>>> capitalize_all(t)
['Alice', 'Bob', 'Carol']
>>> len(t), len(capitalize_all(t))
(3, 3)
AspectWhat happensNote
each elementtransformed by capitalizeone output each
the lengthunchanged3 in, 3 out
the originaluntoucheda new list was built

Notice the unconditional append.

Why: Every element produces exactly one output, because the append is not inside an if.

Check the length.

Why: A map always produces a list the same length as its input. That is the defining property.

Notice the original is unchanged.

Why: capitalize is a string method, which returns a new string, and the results go into a new list. Nothing modifies t.

Figure (svg): A diagram showing three elements each transformed independently into three output elements

Each element is transformed on its own; nothing depends on the others.

Three names in, three capitalized names out. A map maps a function onto each element, and the length is preserved.

Verify: Check the length property on the empty list.

Why: capitalize_all([]) gives [] — length zero in, length zero out. The property holds at the boundary, which is a quick confirmation that the loop has no special cases hiding in it.

29. Predict: how long is the result?

Prediction

The append is inside the condition.

def positives(t):
    res = []
    for x in t:
        if x > 0:
            res.append(x)
    return res
StepWhat happensNote
input [1, -2, 3]one fails the testskipped
the appendstwo happentwo elements
the resultshorter than the inputa filter

Predict first

What is the length of positives([1, -2, 3, -4])?

  • 4
  • 2
  • 3
  • 0

Correct: 2 — only the two positive elements are appended, so the output is shorter than the input.

Why: The append is inside the if, so an element that fails the test produces nothing at all. That is what makes this a filter rather than a map: the output length depends on the data, whereas a map's output is always the same length as its input.

30. Worked example: a filter can shorten

Worked example

The append is inside a condition, so some elements produce nothing.

>>> t = ['ABC', 'def', 'GHI']
>>> only_upper(t)
['ABC', 'GHI']
>>> len(t), len(only_upper(t))
(3, 2)
ElementThe testResult
'ABC'isupper is Trueappended
'def'isupper is Falseskipped
'GHI'isupper is Trueappended

Notice the conditional append.

Why: The append is inside an if, so an element only reaches the output if it passes the test.

Check the length.

Why: Two out of three, so the output is shorter. A filter selects some of the elements and filters out the others.

Notice the elements are unchanged.

Why: A filter selects; it does not transform. The strings that survive are exactly the ones that went in.

Figure (svg): The state of the program after each line of Worked example a filter can shorten, drawn as a ladder with one rung per traced line

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

Two elements out of three. A filter's output is a sublist of its input — same elements, possibly fewer of them.

Verify: Check the two extremes.

Why: A filter whose test is always true returns a list the same length as the input, and one whose test is always false returns an empty list. If a filter can never produce either extreme, its condition is not actually filtering — which is the same check as lesson 8b's counter that always equals the length.

31. Trap: writing a map when a filter was meant

Trap

The trap

A student wants only the positive numbers and writes a loop that appends every element, with an if that only decides what to append.

Put the condition in the wrong place

Why: The if is present, so the filtering feels done.

If the append happens on both branches, the output has one element per input and is a map. The condition changed the values rather than the selection.

The fix

A filter puts the append INSIDE the condition.

Ask what happens to an element that fails the test

Why: In a filter, nothing at all — no append, no output.

Check the length of the result

Why: A filter can produce a shorter list; a map never can.

The length is the reliable test. If your filter always returns a list the same length as its input, the append has escaped from the condition — which is the same structural mistake as a return escaping from an if in lesson 6a.

32. Sort: map, filter or reduce?

Sorting

The shape of the output decides.

Sort into buckets

For each operation on a list, which pattern is it?

map
square every element; convert every element to a string
filter
keep only the even elements; drop the elements that are None
reduce
add up the elements; find the longest element
map
Each transforms every element independently and produces a list of exactly the same length.
filter
Each selects a subset without changing any element, so the output is a sublist that may be shorter.
reduce
Each produces a single value from the whole list, whatever its length.

33. Complete it: write a filter

Faded example

The append goes inside the condition.

Fill in the blanks

def long_words(t):
res = []
for w in t:
if len(w) > 5:
res.append(w)
return res

Why: append adds one element to the accumulator, and its position inside the if is what makes this a filter rather than a map. Note that append modifies res and returns None, so it must be called on its own rather than assigned — res = res.append(w) would be the t = t.sort() mistake in a new place.

34. Explain it: how do I tell a map from a filter?

Explain it

One structural difference, and one observable one.

Discussion prompt

A classmate cannot remember which is which. Give them one thing to look for in the code and one thing to check in the output.

Hint: The code test is about where the append is.

Answer:

In the code: look at whether the append is inside a condition. If every element is appended, it is a map; if only some are, it is a filter.

In the output: check the length. A map always gives a list the same length as its input, and a filter can give a shorter one.

Then add why it is worth telling them apart: chapter 19 provides a compact syntax that does both, and knowing which you want is what makes that syntax readable rather than cryptic.

35. Four ways to delete an element

Section

Section 4

36. pop, del, remove, and slice assignment

Concept

There are several ways to delete elements from a list, and which one you use depends on what you know: the index, the value, or a range.

>>> t = ['a', 'b', 'c']
>>> x = t.pop(1)
>>> t, x
(['a', 'c'], 'b')
>>> t = ['a', 'b', 'c']
>>> del t[1]
>>> t
['a', 'c']
>>> t = ['a', 'b', 'c']
>>> t.remove('b')
>>> t
['a', 'c']
FormWhen to use itWhat it returns
pop(i)you know the index and want the value backmodifies and RETURNS the element
del t[i]you know the index and do not need the valuemodifies, returns nothing
remove(x)you know the value but not the indexmodifies, returns None

pop modifies the list and returns the element that was removed; if you do not provide an index it deletes and returns the last element. The return value from remove is None. To remove more than one element you can use del with a slice.

Think Python, 2nd edition — Allen B. Downey §10.6-10.9, pp. 94-94

37. Picture it: four tools, chosen by what you know

Picture it

The choice is not arbitrary — each fits a different starting point.

Figure (svg): Two columns matching each deletion method to the situation it fits

Four situations, four tools, and only pop hands anything back.

The book's debugging advice applies directly: pick an idiom and stick with it. Part of the problem with lists is that there are too many ways to do things.

38. Worked example: pop returns what it removed

Worked example

The only one of the four that gives you the element back.

>>> t = ['a', 'b', 'c']
>>> x = t.pop(1)
>>> t
['a', 'c']
>>> x
'b'
>>> t.pop()
'c'
CallWhat it doesResult
t.pop(1)removes and returns the element at index 1'b'
the listone element shorter['a', 'c']
t.pop()no index: removes and returns the LAST'c'

Notice that it is both.

Why: pop modifies the list AND returns the element that was removed, which makes it the only non-void method in this group.

Assign the result if you want it.

Why: Unlike the void methods, pop's return value is worth keeping — that is the whole reason to choose it over del.

Note the no-argument form.

Why: If you do not provide an index, it deletes and returns the last element, which is how a list is used as a stack.

Figure (svg): A three-element list with the middle element highlighted as the one being removed by pop

The element is removed and returned; the ones after it shift down.

The list loses one element and the caller receives it. pop is for when the removed value is still wanted; del is for when it is not.

Verify: Compare with del on the same list.

Why: del t[1] removes the same element and gives you nothing — it is a statement rather than an expression, so x = del t[1] is a syntax error. That difference is what makes the choice between them meaningful rather than stylistic.

39. Predict: what does pop return?

Prediction

It is the only one of the four that returns something useful.

t = ['a', 'b', 'c']
x = t.pop()
print(x, t)
StepWhat happensResult
t.pop()no index givenremoves the last element
xthe removed element'c'
tone shorter['a', 'b']

Predict first

What does this print?

  • a and then ['b', 'c'] — the first element removed
  • c and then ['a', 'b'] — the last element removed
  • None and then ['a', 'b'] — nothing given back
  • c and then ['a', 'b', 'c'] — the list left alone

Correct: c and then ['a', 'b'] — the last element removed, and handed back to you.

Why: pop is both a modification and an expression: the list loses its last element and the call produces it. That dual nature is what distinguishes it from del and remove, both of which give you nothing back — and it is what makes a list usable as a stack.

40. Worked example: deleting a range

Worked example

del with a slice removes several elements at once.

>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> del t[1:5]
>>> t
['a', 'f']
PartWhat it selectsResult
t[1:5]indices 1, 2, 3 and 4four elements
delremoves all of themin place
the resulttwo elements left['a', 'f']

Read the slice as always.

Why: As usual, the slice selects all the elements up to but not including the second index — so indices 1 to 4.

Apply del to it.

Why: All four elements are removed and the list closes up.

Check the length.

Why: Six minus four is two, which is the subtraction from lesson 8b arriving in a new place.

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

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

['a', 'f']. Four elements are removed at once, and the half-open convention means the count is simply the difference of the indices.

Verify: Compare with slice assignment to an empty list.

Why: t[1:5] = [] has exactly the same effect, which is a fifth way to delete elements. That is precisely what the book means about there being too many ways to do things — and it is why picking one idiom matters more than knowing all of them.

41. Trap: modifying a list while traversing it

Trap

The trap

A loop over a list removes elements as it goes, and some elements are silently skipped.

Traverse and delete in the same loop

Why: It seems efficient, and the code reads naturally.

Removing an element shifts everything after it down by one, while the loop's position advances — so the element immediately after each removal is never examined.

The fix

Do not modify a list while iterating over it.

Build a new list with a filter instead

Why: Which produces the elements you want to keep, and leaves the original alone until you are done.

Or iterate over a copy

Why: for x in t[:] traverses a copy while modifying t, which is a use for the slice-copy idiom from lesson 10a.

The symptom is characteristic: roughly half the elements that should have been removed are still there, because every removal causes one element to be skipped.

42. Match each situation to the right deletion

Matching

Four situations, four tools.

Match the pairs

  • a. you know the index and want the value back
  • b. you know the index and do not need the value
  • c. you know the value but not where it is
  • d. you want to remove a whole range
  • r1. t.pop(i)
  • r2. del t[i]
  • r3. t.remove(x)
  • r4. del t[i:j]

Why: Each fits a different starting point, which is what makes four tools defensible rather than redundant. Note that remove deletes only the FIRST matching element and raises a ValueError if the value is absent — two details worth knowing before choosing it.

43. Predict: what does removing during a loop do?

Prediction

The positions shift while the loop advances.

t = [1, 2, 2, 3]
for x in t:
    if x == 2:
        t.remove(x)
print(t)
PassWhat happensConsequence
position 0x is 1, keptadvance
position 1x is 2, removedeverything shifts down
position 2now holds 3, not the second 2the second 2 is skipped

Predict first

What does this print?

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

Correct: [1, 2, 3] — one of the twos is skipped, because removing an element shifts the rest down while the loop's position advances.

Why: After the first 2 is removed the list is [1, 2, 3], and the loop moves to position 2, which now holds 3 — so the second 2 is never examined. The symptom is characteristic: roughly half the targets survive. The fix is to build a new list with a filter, or to iterate over a copy with t[:].

44. Why are there so many ways to delete?

Real world

The book calls it part of the problem with lists.

Discussion prompt

Four ways to remove an element, plus slice assignment, is arguably too many. Suggest why a language ends up like this, and what a programmer should do about it.

Hint: Each was added for a reason that seemed good at the time.

Answer:

Each fits a genuinely different situation — index versus value, wanting the element back or not — and each was reasonable to add on its own.

Collectively they are a burden: a reader has to know all of them, and a writer has to choose, which is a decision with no interesting content.

The book's advice is the practical answer: pick an idiom and stick with it. Consistency within a program is worth more than choosing the theoretically best tool for each individual case, because it removes the question entirely.

45. Between lists and strings: list, split and join

Section

Section 5

46. Converting in both directions

Concept

A string is a sequence of characters and a list is a sequence of values, but a list of characters is not the same as a string. Three operations convert between them.

>>> list('spam')
['s', 'p', 'a', 'm']
>>> 'pining for the fjords'.split()
['pining', 'for', 'the', 'fjords']
>>> ' '.join(['pining', 'for', 'the', 'fjords'])
'pining for the fjords'
OperationWhat it doesDirection
list(s)breaks a string into charactersone element per character
s.split()breaks a string into wordsone element per word
d.join(t)the inverse of splitone string from a list

Because list is the name of a built-in function, you should avoid using it as a variable name — which is why the book uses t. join is a string method, so you have to invoke it on the delimiter and pass the list as a parameter.

Think Python, 2nd edition — Allen B. Downey §10.6-10.9, pp. 94-95

47. Picture it: three conversions

Picture it

Two directions, and split has an optional delimiter.

Figure (svg): A diagram showing a string converted to a list of characters or words and a list joined back into a string

list and split go one way; join goes back.

join being a string method rather than a list method is the detail people trip on — it is invoked on the delimiter, not on the list.

48. Worked example: split with a delimiter

Worked example

By default it splits on whitespace. An argument changes that.

>>> 'pining for the fjords'.split()
['pining', 'for', 'the', 'fjords']
>>> 'spam-spam-spam'.split('-')
['spam', 'spam', 'spam']
CallWhat it splits onResult
split()no argument: splits on whitespacefour words
split('-')an explicit delimiterthree parts
the delimiterspecifies the word boundariesand is not included

Use the default for words.

Why: With no argument, split breaks a string at whitespace, which is the common case for text.

Supply a delimiter for anything else.

Why: An optional argument called a delimiter specifies which characters to use as word boundaries.

Notice the delimiter is not in the result.

Why: The hyphens are gone from the output — they were boundaries, not content, which is the same distinction as quotation marks in lesson 1a.

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

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

Four words with the default, three parts with an explicit hyphen. The delimiter marks the boundaries and does not appear in the result.

Verify: Split on a delimiter that is not present.

Why: 'abc'.split('-') gives ['abc'] — a one-element list containing the whole string. That splitting always produces at least one element is worth knowing, because it means the result can be traversed without a special case for no delimiter found.

49. Predict: what does join produce?

Prediction

The delimiter goes between the elements, not around them.

Predict first

What does '-'.join(['a', 'b', 'c']) produce?

  • 'a-b-c'
  • '-a-b-c-'
  • 'abc'
  • 'a-b-c-'

Correct: 'a-b-c' — the delimiter goes between each pair of elements, so three elements have two delimiters.

Why: This is the fencepost count again: n elements have n-1 gaps between them. The delimiter does not appear at the start or the end, which is what makes join the exact inverse of split — splitting 'a-b-c' on the hyphen gives back the three elements.

50. Worked example: join is invoked on the delimiter

Worked example

The surprising part of the syntax, and it follows from join being a string method.

>>> t = ['pining', 'for', 'the', 'fjords']
>>> ' '.join(t)
'pining for the fjords'
>>> ''.join(t)
'piningforthefjords'
>>> '-'.join(t)
'pining-for-the-fjords'
CallThe delimiterResult
' '.join(t)a space between each pairthe usual case
''.join(t)nothing between themconcatenation
'-'.join(t)hyphens between themany delimiter

Notice which object it is called on.

Why: join is a string method, so you have to invoke it on the delimiter and pass the list as a parameter.

See why that makes sense.

Why: The delimiter is a string, and methods belong to the type they operate on. It is join's own argument that is the list.

Use the empty string to concatenate.

Why: To concatenate strings without spaces, you can use the empty string as a delimiter.

Figure (svg): Two columns contrasting the intuitive but wrong join syntax with the correct one

The delimiter goes before the dot. It is a string method, and the string is the delimiter.

The delimiter goes before the dot and the list goes inside the parentheses. It reads oddly the first time and follows directly from join being a string method.

Verify: Check that join and split are inverses.

Why: ' '.join('a b c'.split()) gives back 'a b c'. join is the inverse of split, and confirming a round trip is the quickest way to check you have the delimiter right in both directions.

51. Trap: building a string by repeated concatenation

Trap

The trap

A student builds a long string by concatenating in a loop, one piece at a time.

Use the string accumulator from lesson 8b

Why: It works, and it was the only option before lists existed.

Every concatenation copies the whole string so far, so building a string of n pieces copies roughly n squared characters. For a few pieces it is fine and for thousands it is slow.

The fix

Accumulate into a list, then join once at the end.

append each piece to a list

Why: Which modifies the list in place rather than copying anything.

Then call join on the delimiter

Why: One pass, one new string, however many pieces there were.

This is the standard idiom for building text in Python, and it is a direct consequence of the immutability difference from lesson 10a: appending to a mutable list is cheap and rebuilding an immutable string is not.

52. Match each conversion to what it produces

Matching

Three operations, two directions.

Match the pairs

  • a. list('spam')
  • b. 'a b c'.split()
  • c. ' '.join(['a', 'b'])
  • d. 'a-b'.split('-')
  • r1. ['s', 'p', 'a', 'm']
  • r2. ['a', 'b', 'c']
  • r3. 'a b'
  • r4. ['a', 'b']

Why: list breaks a string into individual characters and split breaks it into pieces at a delimiter, so the two differ in granularity rather than in kind. join goes the other way, and it is invoked on the delimiter — which is the piece of syntax worth rehearsing, since ['a','b'].join(' ') is an AttributeError.

53. Complete it: join a list of words

Faded example

The delimiter goes before the dot.

Fill in the blanks

words = ['hello', 'there']
sentence = ' '.join(words) # should be 'hello there'

Why: join is a string method invoked on the delimiter, with the list passed as its argument — so a single space produces a space between each pair of words. Writing words.join(' ') is the intuitive form and raises an AttributeError, because lists have no join method. The delimiter is the string, and methods belong to the type they operate on.

54. Explain it: why is join a string method?

Explain it

It looks backwards until you say it out loud.

Discussion prompt

A classmate keeps writing t.join(' ') and getting an AttributeError. Explain why join belongs to strings rather than to lists, and give them a way to remember the order.

Hint: Which of the two things is a string?

Answer:

Say: join produces a string, and the delimiter is a string — so it is a string method. The list is what it takes as an argument.

The way to remember it: read it as put this between them. The delimiter is the thing doing the putting, so it goes first.

It is also worth noting that this design lets join work on any sequence of strings, not just lists — which it could not do if it were a list method. Attaching it to the delimiter is what makes it general.

55. Compare: the three patterns

Comparison

Fill the blanks. The shape of the output distinguishes them.

Comparison matrix

PatternWhat it does to each elementThe output
maptransforms ita list of the same length
filterkeeps it or drops it, unchangeda list the same length or shorter
reducecombines it into a running resulta single value

Most common list operations can be expressed as a combination of the three, which is why naming them is worth a section.

56. The procedure: choosing a list operation

Pattern

Six steps, and the second is the one that prevents the chapter's commonest bug.

  1. Decide what shape the result should be: another list of the same length, a shorter list, or a single value.
  2. Decide whether the original should change — and if it should not, build a new list rather than using a method that modifies.
  3. For a method that modifies, call it on its own line and do NOT assign the result, because it returns None.
  4. For a function that returns, assign the result — sorted, sum, and the list you built yourself.
  5. When deleting, choose by what you know: pop for an index whose value you want, del for an index you do not, remove for a value.
  6. Never modify a list while iterating over it — build a new one, or iterate over a copy.

Step 3 is the t = t.sort() rule, and it is worth stating as a rule rather than a caution: if a method modifies, its return value is None and assigning it destroys your reference.

Python documentation — Data Structures Data Structures

57. Check yourself 1 of 3: void methods

Check

The method modifies and returns None.

t = [3, 1, 2]
t = t.sort()
StepWhat happensResult
t.sort()sorts in placethe list is sorted
returns Nonesort is a void methodnothing useful
t = Nonethe name is repointedthe list is lost

Check your understanding

What does t hold after these two lines?

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

Answer: B

Why: sort modifies the list in place and returns None, so the assignment stores None in t and the sorted list becomes unreachable. The correct usage is t.sort() with no assignment. This is the single most common mistake in the chapter, and the book warns about it explicitly.

Why A tempts people
The list really was sorted — but t no longer refers to it, because the assignment repointed the name at the method's return value.
Why C tempts people
The sort did happen. The list was reordered before the assignment discarded the reference to it.
Why D tempts people
No error is raised at this point. The failure comes later, when the next operation is attempted on None.

58. Check yourself 2 of 3: map versus filter

Check

The position of the append decides.

def f(t):
    res = []
    for x in t:
        if x > 0:
            res.append(x)
    return res
FeatureWhat it meansNote
the appendinside the ifnot every element
the outputmay be shortera filter
the elementsunchangedselected, not transformed

Check your understanding

Which pattern is this?

  • A. A map
  • B. A filter (correct)
  • C. A reduce
  • D. None of the three

Answer: B

Why: The append is inside the condition, so only some elements reach the output and the result may be shorter than the input. The elements themselves are unchanged, which is what distinguishes a filter from a map — a map transforms every element and always produces a list of the same length.

Why A tempts people
A map appends unconditionally and produces one output per input, so its result is always the same length as its input.
Why C tempts people
A reduce produces a single value rather than a list. This one returns a list.
Why D tempts people
It is a textbook filter: traverse, test, and select the elements that pass.

59. Check yourself 3 of 3: join

Check

It is a string method, invoked on the delimiter.

Check your understanding

Which of these joins a list of strings with commas?

  • A. t.join(',')
  • B. ','.join(t) (correct)
  • C. join(t, ',')
  • D. t + ','

Answer: B

Why: join is a string method, so it is invoked on the delimiter with the list passed as an argument. The delimiter goes before the dot, which reads oddly at first and follows directly from join belonging to the string type.

Why A tempts people
Lists have no join method, so this raises an AttributeError. It is the intuitive form and it is the wrong way round.
Why C tempts people
There is no such built-in function. join exists only as a string method.
Why D tempts people
This would try to concatenate a list and a string, which raises a TypeError — and even if it worked it would not put commas between the elements.

60. Where this shows up outside this course

Real world

Map, filter and reduce are how nearly all data processing is described.

Discussion prompt

Think of a report you have produced from data — a spreadsheet, a query, a summary. Break what you did into the three patterns, and say which order they came in.

Hint: Filtering usually comes before reducing.

Answer:

A typical report filters the rows you care about, maps each to the fields you want, and reduces to a total or an average — in that order.

The order matters for cost as well as meaning: filtering first means the map and reduce handle fewer rows, which is why database queries are written the same way.

Spreadsheet formulas and SQL both name these operations explicitly — WHERE is a filter, SELECT is a map, SUM is a reduce. Recognising the three in Python is what makes those languages feel familiar rather than foreign.

61. Confidence wager: commit before you check

Commit first

Answer, then rate your confidence. Three of these run without error.

Predict first

Which of these four lines raises an error, for a list t and an element x?

  • t.append([x])
  • t = t.append(x)
  • t + [x]
  • t = t + x

Correct: t = t + x — you cannot concatenate a list and a non-list, so it raises a TypeError.

Why: The book lists all four as wrong ways to add an element and notes that only the last causes a runtime error; the other three are legal and do the wrong thing. append with a list argument adds a nested list; assigning append's return value stores None and loses the list; and a bare concatenation computes a new list and discards it. That three of four failures are silent is exactly why the book's advice is to try each one in interactive mode and see what it does — and then to pick one correct idiom and stick with it.

62. Explain it to someone else

Explain it

The void-method rule is the thing a student coming from strings most needs.

Discussion prompt

A classmate writes t = t.sort() and then gets a TypeError about NoneType on the next line. Explain what happened, what the fix is, and how they could recognise this class of bug in future.

Hint: The error appears one line after the mistake.

Answer:

Say: sort modifies the list in place and returns None, so the assignment stored None in t. The sorting worked; the assignment threw away the reference.

The fix is t.sort() with no assignment — or t = sorted(t), which uses the function that returns a new list.

How to recognise it: a TypeError mentioning NoneType nearly always means the return value of a void operation was assigned or used. Look one line up from the failure for a method call on the right of an equals sign — which is the same diagnostic as lesson 6a's missing return, in a new place.

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?

  • The list methods, and the fact that most of them return None
  • The three patterns: map, filter and reduce
  • The four ways to delete an element, and choosing between them
  • Converting between strings and lists with list, split and join

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

Why: The void-method rule is the highest-value item here, because its failure is immediate and its cause is one line earlier than the error. The three patterns are worth naming now because chapter 19 provides syntax for all of them and it is unreadable without the names. The deletion methods are four tools for four situations and the book's own advice is to pick one and stick with it. And the conversions are mostly syntax, except for join being invoked on the delimiter — which nearly everybody gets wrong once.

64. Synthesis: draw the map of this lesson

Connect it up

One page, from memory.

Draw it

Draw three boxes for map, filter and reduce, and under each write what shape of output it produces and one example operation. Then, beside them, make two columns listing every list method from this lesson: those that modify and return None, and those that return something worth keeping. Finally, write out the four wrong ways to append an element and mark the one that raises an error — and say what the other three do instead.

65. What you can do now

Recap

Three pages, and most of what is done with lists in practice.

If you remember one thingIt is this
From the methodsMost modify and return None. Assigning the result destroys your list.
From the patternsMap keeps the length, filter can shorten, reduce gives one value.
From deletingNever modify a list while iterating over it.
From joinThe delimiter goes before the dot. Lists have no join method.
From the four wrong appendsThree of them are legal. Legality is not correctness.

The next lesson explains why the difference between modifying and creating matters so much: when two names refer to the same list, a change made through one is visible through the other — which is powerful, error-prone, and the reason this chapter exists.

Think Python, 2nd edition — Allen B. Downey §10.6-10.9, pp. 92-94 — 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.6-10.9, pp. 92-94
  2. Python documentation — Data Structures
  3. Python documentation — Built-in Functions

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

Book on Wyzant · Text (657) 465-8108