Skip to content
VLSI Mentor

Python Part I · Module 5 · Lists

Changing a List: append, insert, remove, pop

This is the chapter the module exists for. Every string method in Module 4 handed back a new string and left yours untouched, because a string cannot be changed — and that rule was stated so firmly that it is worth saying plainly now that lists do not follow it. A list can be changed after it is created. You can replace an element, add one, take one out, and the list you already have is afterwards a different list. Nothing is returned that you need to catch, and no name needs reassigning. That difference is simple to state and takes real care to use correctly, because one of the most common beginner bugs in Python comes from assuming these methods behave like the string ones.

Foundation16 min readPythonPart IFundamentalsListsMutation

Part I · Module 5 · Chapter 5.3 · Changing a List: append, insert, remove, pop

1. The Problem

A script finds out things as it goes. It starts knowing nothing about which tests failed, and by the end it needs a list of them. A report starts empty and gains rows.

That is impossible with the tools you have so far. Everything Module 4 taught produced a new value: line.strip() gave you a new string, ",".join(parts) gave you a new string. To grow a collection you would have to rebuild it every time.

Lists work differently, and this chapter is that difference.

2. Replacing an Element

Here is the demonstration that makes the word "mutable" mean something. Assign to a position, exactly as you would read from one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset", "interrupt"]
print(tests)
tests[1] = "boot"
print(tests)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['smoke', 'reset', 'interrupt']
['smoke', 'boot', 'interrupt']

Position 1 held "reset" and now holds "boot". Nothing was reassigned — there is no tests = on that middle line. The list itself changed.

Now put that beside the string it is the opposite of. Chapter 4.11 established this and it is worth seeing the two together:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
text = "PASS"
text[0] = "F"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TypeError: 'str' object does not support item assignment

Identical syntax. On a list it works; on a string it is a TypeError, because a string has no operation for having one of its items replaced. That is the whole of the mutable/immutable distinction Chapter 2.13 introduced, now visible in two lines of code.

This chapter uses one grammar throughout, and it is worth naming because it is the only reliable way to reason about mutation: print the list, do the operation, print it again. Before and after. When a list changes in place, the change is only visible if you look.

3. Mutation Is Not Rebinding

These two look similar and are completely different operations. Both end with a referring to a list starting with "boot":

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a = ["smoke", "reset"]
a[0] = "boot"
print(a)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['boot', 'reset']
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
b = ["smoke", "reset"]
b = ["boot"]
print(b)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['boot']

The first mutated a list: the same list value that a referred to before now has a different element at position 0. The second rebound a name: a brand-new list was built and b was pointed at it, and the old list is simply no longer what b refers to.

The difference is invisible when only one name is involved — which is exactly why it is worth establishing now, before it starts mattering:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]
before = tests
tests[0] = "boot"
print(tests)
print(before)
print(tests == before)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['boot', 'reset']
['boot', 'reset']
True

Look at the second line of output. before was never assigned to again, and it shows "boot" as well.

That is not a mistake, and it is not something to work around. before = tests did not take a snapshot — it gave a second name to the same list, and mutating that list is visible through either name. Chapter 5.8 is devoted to this, and it is one of the most useful things in Part I. For now, notice only that mutation is visible everywhere the list is reachable, and rebinding is not.

One failure worth seeing, because the message is specific:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
tests = ["smoke", "reset"]
tests[5] = "x"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
IndexError: list assignment index out of range

Assignment reaches an existing position; it does not create one. A two-element list has positions 0 and 1, and there is no position 5 to assign to. Note list assignment index out of range rather than the plain list index out of range from Chapter 5.2 — Python is telling you which operation failed. To make the list longer you add to it, which is the next section.

4. append(): One Element on the End

The most common list operation there is. A script starts with nothing and accumulates:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
failures = []
print(failures)
failures.append("axi_burst")
print(failures)
failures.append("reg_rw")
print(failures)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[]
['axi_burst']
['axi_burst', 'reg_rw']

append is read exactly as Chapter 4.1 taught method calls: ask this list to perform its append operation with this value. The list gains one element on the end, and the list you already had is the one that changed.

The word one is load-bearing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke"]
tests.append(["reset", "burst"])
print(tests)
print(len(tests))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['smoke', ['reset', 'burst']]
2

That is not three tests. append was handed one value — a list — so it added one element, and that element happens to be a list. The result has two elements: a string and a list. Chapter 5.6 covers the operation that adds each of several values instead, and Chapter 5.7 covers lists that hold lists deliberately.

append takes exactly one value, and says so if given more:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
tests = []
tests.append("a", "b")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TypeError: list.append() takes exactly one argument (2 given)

5. The Trap: append() Returns None

This is the most expensive misunderstanding in the module, and it deserves the space.

append changes the list. It does not hand the list back:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]
result = tests.append("interrupt")
print(result)
print(tests)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
None
['smoke', 'reset', 'interrupt']

The list gained its element — look at the second line. But result is None.

Now the version that breaks real scripts, because it looks exactly like correct Module 4 code:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
tests = ["smoke", "reset"]
tests = tests.append("interrupt")
print(tests)
print(type(tests))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
None
<class 'NoneType'>

No error. No warning. tests is now None, and the next thing that touches it fails somewhere else entirely with a message that says nothing about this line.

It is worth being precise about why, because "append returns None" stated as a bare fact does not stop anyone writing this. Three things happen, in order:

  1. tests.append("interrupt") mutates the list. The list genuinely gains its element — that part works.
  2. The expression tests.append("interrupt") evaluates to None. Chapter 3.1 established that every expression produces a value; this one produces None because there is nothing useful to hand back — the change already happened to the list you have.
  3. tests = ... then rebinds the name to that None, exactly as Chapter 2.3 says assignment does. The list still exists and is still correct; tests just no longer refers to it.

So the list was never the problem. The assignment was.

This is the precise opposite of the habit Module 4 spent thirteen chapters building. There, a method call alone on a line did nothing and you had to store the result. Here, storing the result is the bug. The rule that covers both:

An immutable value's methods return a new value — keep it. A mutable value's methods change the value — do not.

6. insert(): Putting One in the Middle

append only ever adds at the end. When position matters, insert takes the position first and the value second:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "interrupt"]
print(tests)
tests.insert(1, "reset")
print(tests)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['smoke', 'interrupt']
['smoke', 'reset', 'interrupt']

Read insert(1, "reset") as put this value at position 1. Afterwards "reset" is at position 1, and — this is the part to be clear about — "interrupt" did not disappear. It moved from position 1 to position 2. The list is now three elements long:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "interrupt"]
tests.insert(1, "reset")
print(len(tests))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3

insert adds; it never replaces. Replacement is tests[1] = "reset" from Section 2, which keeps the length the same. Those two are easy to confuse and do different things.

The boundaries are forgiving:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
t2 = ["b", "c"]
t2.insert(0, "a")
print(t2)
t2.insert(99, "z")
print(t2)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['a', 'b', 'c']
['a', 'b', 'c', 'z']

Position 0 puts the element at the front. A position past the end does not raise — it appends. That is deliberate, but it means a wrong position number gives you a quietly misplaced element rather than an error.

7. remove(): Taking Out a Value You Name

remove is given a value, not a position, and takes out the first element equal to it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
statuses = ["PASS", "FAIL", "PASS"]
print(statuses)
statuses.remove("PASS")
print(statuses)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['PASS', 'FAIL', 'PASS']
['FAIL', 'PASS']

Two things to read carefully there. The list started with three elements and ends with two — so one was removed, not both matches. And the one removed was the first "PASS", which is why the survivor is at the end.

Like every mutating method here, it returns nothing useful:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
s2 = ["a", "b"]
r = s2.remove("a")
print(r, s2)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
None ['b']

And asking it to remove something that is not there is an error rather than a silent no-op:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
tests = ["smoke"]
tests.remove("reset")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ValueError: list.remove(x): x not in list

A ValueError, not an IndexError — the argument was a perfectly good value, it just was not in the list. The message even shows you the method's shape. Until Module 9 gives you if, the way to be safe is to check membership with in from Chapter 4.6 before removing.

8. pop(): Remove and Hand It Back

pop is the one mutating method in this chapter that returns something you want. It removes an element and gives it to you:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset", "interrupt"]
removed = tests.pop()
print(removed)
print(tests)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
interrupt
['smoke', 'reset']

With no argument it takes the last element. removed is that element, and the list is now shorter.

Give it a position and it takes that one instead:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
t3 = ["smoke", "reset", "interrupt"]
first = t3.pop(0)
print(first)
print(t3)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke
['reset', 'interrupt']

So pop is the method where assignment is correct: removed = tests.pop() is right, and tests = tests.pop() is the bug — it would set tests to the removed element.

Both failures are worth seeing, because the messages differ:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
print([].pop())
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
IndexError: pop from empty list
Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
print(["a"].pop(5))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
IndexError: pop index out of range

The first is the one that bites in practice: a list of failures is legitimately empty when nothing failed, and popping from it raises.

9. clear(), and the Pattern All of This Is For

clear empties a list in place. The list still exists and is still the same list — it now has no elements:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
t4 = ["a", "b"]
t4.clear()
print(t4, len(t4))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[] 0

Now the pattern the canonical curriculum names for this chapter, and the reason append matters more than the rest put together. A script begins knowing nothing and accumulates as it learns:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
failures = []
failures.append("axi_burst")
failures.append("reg_rw")
print(failures)
print(len(failures))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['axi_burst', 'reg_rw']
2

Start empty, add as you find, report at the end. That is the shape of nearly every checking script ever written.

What is missing is the part that decides when to append — a script does not know its failures in advance, it works through results and adds the ones that failed. Deciding is if (Module 9) and working through is a loop (Module 10). This chapter owns the accumulation itself, which is the piece that does not change when those arrive: failures = [] before, failures.append(...) as you go, len(failures) after.

10. What Each Operation Does

Now that each has arrived from a problem, here they are together. This is a summary of what you have seen, not the lesson:

OperationChanges the list?Evaluates toNotes
tests[1] = "boot"yes — replaces—length unchanged; position must exist
tests.append(x)yes — adds one at the endNoneone element, whatever x is
tests.insert(i, x)yes — adds at position iNoneshifts the rest along; never replaces
tests.remove(x)yes — takes out first matchNoneValueError if absent
tests.pop()yes — takes off the lastthe elementpop(i) for a position
tests.clear()yes — empties itNonesame list, no elements

The only row where assignment is correct is pop.

11. Predict, Run, Observe

Predict every printed line before reading on. Three of these are the traps from this chapter.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
jobs = ["smoke", "reset", "smoke"]
jobs.append("burst")
print(jobs)
jobs.remove("smoke")
print(jobs)
last = jobs.pop()
print(last)
print(jobs)
jobs.insert(0, "boot")
print(jobs)
print(jobs.append("x"))
print(len(jobs))

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['smoke', 'reset', 'smoke', 'burst']
['reset', 'smoke', 'burst']
burst
['reset', 'smoke']
['boot', 'reset', 'smoke']
None
4

Follow it through. append("burst") puts one element on the end. remove("smoke") takes out the first "smoke" only — the second survives, which is Section 7. pop() returns "burst" and shortens the list. insert(0, "boot") puts an element at the front and shifts the rest.

The sixth line is None because print(jobs.append("x")) prints what the call evaluates to, not the list — Section 5. And the last line is 4, not 3: that append really did add "x" to the list even though the print showed None. Both things are true at once, and that is exactly why the trap is dangerous.

12. Exercises

Exercise 1 — before and after. For signals = ["clk", "valid"], write the four statements that: add "ready" at the end, insert "reset_n" at position 1, replace "valid" with "vld", and remove "clk". Print the list after each, and predict each result before running.

Exercise 2 — mutation or rebinding. For each, say which it is and what the list contains afterwards: (a) a[0] = "x" (b) a = ["x"] (c) a.append("x") (d) a = a + ["x"]

Exercise 3 — the None trap. This prints None and then fails a few lines later. Explain the three steps that produced None, and fix it with the smallest possible change:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
failures = []
failures = failures.append("axi_burst")
print(failures)

Exercise 4 — first match only. Given results = ["FAIL", "PASS", "FAIL"], predict the list after one results.remove("FAIL"). Then say how many calls it would take to remove both, and why you cannot simply call it twice on a list where you are unsure how many there are.

Exercise 5 — insert or replace. A colleague wants "reset" to become "boot" in ["smoke", "reset", "burst"] and writes tests.insert(1, "boot"). Say what they actually get, why, and give the line they meant.

Exercise 6 — pop is different. Explain in two sentences why removed = tests.pop() is correct while tests = tests.append(x) is a bug, referring to what each method evaluates to.

Exercise 7 — the empty list. Write the two lines that would make pop() raise, name the exact error, and say when that situation legitimately happens in a real script.

Exercise 8 — one element or several. Predict len(tests) after each, starting from tests = ["smoke"]: (a) tests.append("reset") (b) tests.append(["reset", "burst"]) (c) tests.insert(0, "boot")

13. Summary

  • A list can be changed after it is created. That is what mutable means, and it is the opposite of the string rule from Chapter 4.11.
  • tests[1] = "boot" replaces an element in place. The identical syntax on a string is a TypeError.
  • Mutation changes the value; rebinding changes what a name refers to. tests[0] = x and tests = [x] are different operations.
  • Assignment cannot extend a list — tests[5] = x on a two-element list is IndexError: list assignment index out of range.
  • append(x) adds exactly one element to the end, whatever x is. Appending a list adds one element that is a list.
  • append evaluates to None. tests = tests.append(x) sets tests to None — silently. Write the call on its own line.
  • An immutable value's methods return a new value, so keep it. A mutable value's methods change the value, so do not.
  • insert(i, x) adds at a position and shifts the rest. It never replaces, and a position past the end appends rather than raising.
  • remove(x) takes out the first match only, and raises ValueError: list.remove(x): x not in list if absent.
  • pop() removes and returns the element — the one method here where assignment is right. pop() on an empty list is IndexError: pop from empty list.
  • clear() empties the list in place.
  • Start empty, append as you find, report at the end — the accumulation pattern that most checking scripts are built from.

Mutation changes what is in a list. The next chapter puts what is in it into a useful order.

Where this fits

Part of the Python curriculum.