Topic 4.1
Lists: Ordered, Changeable Collections
In one line
A list holds many values in a fixed order, and you can add, remove and change items after it is created. Each item has a position number (an index) starting at 0, and a list stores references to its items, not copies of them.
Think of it like this
A shopping list on paper. Items are written in order, each on its own line. You can add an item at the bottom, squeeze one in the middle, cross one out, or rewrite one. You can ask "what is the third item?" and "is milk on the list?". A Python list is exactly this: an ordered collection of values that you can change.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- List
- An ordered, changeable collection of values written in square brackets.
- Item (element)
- One value inside a list.
- Index
- The position number of an item. The first item is at index 0; -1 is the last.
- Object
- Any value that Python keeps in memory: a number, a piece of text, a list. Variables are names that point at objects.
- Mutable
- Can be changed after it is created. Lists are mutable; numbers and strings are not.
- In place
- Changing the existing object instead of building a new one.
- Method
- A function that belongs to a value and is called with a dot, like
cart.append(x). - Reference
- An arrow from a name, or from a list slot, to a value in memory.
- Alias
- A second name for the same object.
- IndexError
- The error Python raises when you ask for an index that does not exist.
Step by step
01Make a list and read items
Square brackets with commas make a list. Index 0 is the first item. A negative index counts back from the end, which is handy because you don't need to know the length to reach the last item.
cart = ["milk", "eggs", "bread"]
print(cart[0]) # milk (first)
print(cart[2]) # bread (third)
print(cart[-1]) # bread (last)
print(len(cart)) # 302Add items: append, extend, insert
append adds exactly one item, even if that item is itself a list. extend unpacks another collection and adds each of its items. Mixing them up is a classic bug: cart.append(["tea", "salt"]) adds one item that is a list; cart.extend(["tea", "salt"]) adds two items.
insert(1, "butter") puts "butter" at index 1 and moves the old items from index 1 onward one place to the right.
cart = ["milk", "eggs"]
cart.append("rice") # ['milk', 'eggs', 'rice']
cart.extend(["tea", "salt"]) # adds two items
cart.insert(1, "butter") # now at index 1
cart.append(["a", "b"]) # ONE item that is a list03Remove items: pop, remove, del
pop() takes the last item off and gives it back to you, which makes a list work like a stack of plates. pop(0) takes the first item. remove("eggs") finds an item by value and removes the first match. del cart[0] removes by position and gives nothing back.
If the value is not there, remove raises a ValueError. Check with in first, or catch the error (Topic 7.1).
cart = ["milk", "eggs", "bread", "tea"]
last = cart.pop() # 'tea', and cart loses it
cart.remove("eggs") # by value
del cart[0] # by position
print(cart, last)04Change an item and test membership
Assigning to an index replaces that slot. The list object stays the same object; only one arrow inside it moves. in asks whether any item equals the value.
cart = ["milk", "eggs", "bread"]
cart[1] = "oats"
print(cart) # ['milk', 'oats', 'bread']
print("milk" in cart) # True
print("eggs" not in cart) # True05Two names, one list
b = a copies the arrow, not the list. After it, a and b both point at one list. b.append(4) changes that one list, so a shows the new item too. The word is (an operator, a symbol or word that compares or combines values) asks "are these the very same object?", while == asks "do they hold equal contents?". Two lists can be equal without being the same list, like two printed copies of one shopping list.
a = [1, 2, 3]
b = a # same list, two names
b.append(4)
print(a) # [1, 2, 3, 4]
print(a is b) # True06A list holds references, even to other lists
If you put the same inner list in two slots, both slots point at one inner list. Changing it through one slot shows in the other. Nothing was copied. Knowing that a list stores arrows is the key to understanding copies in Topic 4.9.
row = [0, 0]
grid = [row, row] # two slots, one inner list
row[0] = 9
print(grid) # [[9, 0], [9, 0]]07Methods that return None
append, sort, reverse, extend, insert and remove change the list and return None. If you write cart = cart.append("tea"), the list is changed, then cart is overwritten with None. Python made this choice on purpose so that the name of the method tells you it is a change, not a copy.
08Lists in the AI world
A conversation with a chat model is a list of messages: the first item is the system message, then the user's message, then the model's reply, and so on. The program appends each new message and sends the whole list to the model again. You will build exactly that in Topic 4.8.
Try it yourself
- 1
Predict, then run
In the first example, write on paper what
cartlooks like after each of the three adding lines. Press Run and compare. Check where"butter"landed and why. - 2
Find the alias
In the second example, change
b = atob = [1, 2, 3]and run again. Which line of output changed, and why? Nowaandbare two different lists with equal contents.
Code & diagrams
Predict each printed line before you press Run. Then change an index and see the IndexError message in the Break it section.
Expected output
Start: ['milk', 'eggs', 'bread']
First: milk
Last: bread
Length: 3
After adding: ['milk', 'butter', 'eggs', 'bread', 'rice', 'tea', 'salt']
Popped: salt
After removing: ['milk', 'butter', 'bread', 'rice', 'tea']
Is tea in the cart? True
Is salt in the cart? False
Index of bread: 2No copy is made by b = a. Watch which prints change when only one name is used.
Expected output
a: [1, 2, 3, 4]
b: [1, 2, 3, 4]
same list? True
equal contents? True | same list? False
grid: [[9, 0], [9, 0]]
cart is now: NoneExpected output
append: [1, 2, [3, 4]] -> length 3
extend: [1, 2, 3, 4] -> length 4
extend with a string: [1, 2, 'h', 'i']Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Ask for an index that is not there
A list of three items has indexes 0, 1 and 2. Ask for index 3.
cart = ["milk", "eggs", "bread"]
print(cart[3])Break #2
Remove a value that is not in the list
Call remove with a value the list does not contain.
cart = ["milk", "eggs", "bread"]
cart.remove("sugar")Myth vs fact
Myth
b = a makes a copy of the list.
Fact
It makes a second name for the same list. Copying needs a.copy(), a[:] or list(a) (Topic 4.9).
Myth
Lists must hold items of one type.
Fact
A list can mix types freely: [1, "two", 3.0, None] is valid. It is good style to keep one kind of thing in a list, but Python does not force it.
Myth
cart = cart.append(x) is how you add an item.
Fact
append changes the list in place and returns None, so this overwrites cart with None. Write just cart.append(x).
Myth
Indexes start at 1, like page numbers.
Fact
They start at 0, because an index is a distance from the start: the first item is 0 steps away.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
CPython stores a list as an array of pointers to objects, plus a length and a spare capacity. Reading
cart[i]is therefore one step regardless of the length (O(1)).appendis amortised O(1) because the array over-allocates (it grows by roughly one eighth of its length plus a few slots, so the occasional copy is spread over many appends; the exact formula is a CPython detail) and only occasionally has to be grown;pop()from the end is O(1), whileinsert(0, x)andpop(0)are O(n) because every pointer after the position must be moved. If you need fast work at both ends, usecollections.deque(Topic 4.10). - ▸
inon a list is O(n): it compares item after item. For many membership tests on a large collection, build asetfirst (Topic 4.6). This single change fixes a large share of slow beginner programs. Each comparison checks identity first and==second (x is item or x == item), which is why, withnan = float("nan"),nan in [nan]isTruefor the very same object even thoughnan == nanisFalse. - ▸
Because items are references,
sys.getsizeof(cart)counts only the pointer array, not the objects it points to. A list of a million ints costs about 8 MB for pointers plus the int objects themselves. NumPy arrays (Phase 14) store raw numbers directly, which is why they are far smaller and faster. - ▸
Python's own lists are not the same as the arrays in Java or C. They are closest to Java's
ArrayList<Object>: resizable, indexed, holding references.
Remember this
- 1
You write a list with square brackets and commas:
cart = ["milk", "eggs", "bread"]. The items can be of any type, and the types can be mixed, although a list usually holds one kind of thing. An empty list is[].len(cart)tells you how many items it has. - 2
Every item has a position number called an index, and indexes start at 0, not 1. In
cart,"milk"is at index 0,"eggs"at 1 and"bread"at 2. Negative indexes count from the end:cart[-1]is the last item,cart[-2]the one before it. Asking for an index that does not exist is anIndexError. - 3
Lists are mutable: you can change a list after you make it, and it stays the same list. (In Python, every value that lives in memory is called an object: a number, a piece of text, a list.) Here are the ways to change one.
append(x)adds one item at the end.extend(items)adds every item of another collection at the end.insert(i, x)puts an item at positioniand pushes the rest along.pop()removes and returns the last item (pop(i)removes the one at indexi).remove(x)removes the first item equal toxand raisesValueErrorif there is none.cart[0] = "oats"replaces an item in place. - 4
x in cartisTruewhen some item equalsx, andx not in cartis the opposite. For a list, Python checks the items one by one from the start, so the work grows with the length of the list (Topic 4.11 puts numbers on this).cart.index(x)gives the position of the first match andcart.count(x)how many items equalx. - 5
A list does not hold the values themselves. It holds references (arrows) to value objects, the same way a variable name does (Topic 1.1). So
b = adoes not copy a list: it makes a second name for the same list, and a change through one name shows through the other. A list that contains a list holds an arrow to the inner list, not a copy of it. This one idea explains many beginner bugs; Topic 4.9 goes all the way into it. - 6
Most methods that change a list (
append,extend,insert,remove,sort,reverse) change it in place and returnNone, not the list. Socart = cart.append("tea")throws your list away and leavescartasNone. Method names tell you which kind you have:sorted(x)builds a new list,x.sort()changes the old one.
Explain it without notes
What does it mean that a list is mutable, and how is that different from a string?
What is the difference between append and extend?
Why does changing b also change a after b = a, and how do you get a real copy?
Why is x in my_list slow for big lists, and what would you use instead?
Why is append O(1) but insert(0, x) O(n)? Is append always cheap?
Practice
Make a list scores = [70, 85, 90]. Add 100 at the end, insert 60 at the start, then print the list and its length.
Given tasks = ["wash", "cook", "study", "sleep"], remove the last task and print which one you removed, then print the list.
Make a = [1, 2] and b = a. Append 3 through b. Print both and print whether they are the same list. Then make a real copy c and show that appending to c does not change a.
Trade-offs
- ↔
A list is flexible but a general-purpose tool. It keeps order, allows duplicates and can be changed, which makes it the default container. The price: membership tests are slow on large lists, and inserting or removing at the front is slow. A
set,dictordequefits those jobs better. - ↔
In-place methods are fast but change data other code may be using. If a function receives a list and calls
sort()on it, the caller's list is reordered too. Build a new list withsorted()when the original must stay as it was.
Done when you can
Done when you can create a list, read items by positive and negative index, and say what
lenreturns.Done when you can choose between
append,extend,insert,pop,removeanddelfor a task.Done when you can explain why
cart = cart.append(x)is a bug.Done when you can explain
b = aas two names for one list and name three ways to copy a list.Done when you can read an
IndexErrorandValueErrormessage and say what caused each.