Lists

Beginner35 min

Create, change and sort Python lists, and avoid the aliasing bug that catches almost every beginner.

What you will learn
  • Create lists and read, add, remove and replace items
  • Choose between sort and sorted
  • Explain why two names can refer to the same list
  • Copy a list, and know when a copy is only shallow
On this page

Up to now every variable has held one value. Real programs hold many: the items in a basket, the lines in a file, the scores in a class. The list is Python’s everyday answer. It is an ordered, changeable sequence of values, and you will use it constantly. The shared fundamentals lesson on arrays and lists (linked at the top of this page) explains the general idea of an indexed collection. This lesson covers how Python spells it.

Creating and reading a list

Square brackets make a list. Indexes start at 0, and negative indexes count from the end, so tasks[-1] is the last item.

list_read.py
tasks = ["write", "test", "ship"]
print(tasks)
print(tasks[0], tasks[-1])
print(len(tasks))
print("ship" in tasks, "nap" in tasks)
Output
['write', 'test', 'ship']
write ship
3
True False

Assigning to an index replaces that item, which is the key difference from strings and tuples: a list is mutable.

list_replace.py
tasks = ["write", "test", "ship"]
tasks[0] = "draft"
print(tasks)
Output
['draft', 'test', 'ship']

Reading past the end is an error. Check len(items) first, or use an index you know is there:

tasks = []
print(tasks[0])
Output
IndexError: list index out of range

You already know the loops that go with lists. for task in tasks: visits each item, and enumerate and reversed from the loops lesson work on lists too.

list_loop.py
tasks = ["write", "test", "ship"]
for number, task in enumerate(tasks, start=1):
print(number, task)
for task in reversed(tasks):
print(task, end=" ")
print()
Output
1 write
2 test
3 ship
ship test write

Adding and removing

list_add.py
tasks = ["write", "test"]
tasks.append("ship")
tasks.extend(["celebrate", "rest"])
tasks.insert(0, "plan")
print(tasks)
Output
['plan', 'write', 'test', 'ship', 'celebrate', 'rest']
  • append(x) adds one item at the end. extend(iterable) adds every item from another iterable.
  • insert(i, x) puts an item at position i and shifts the rest along.
list_remove.py
tasks = ["plan", "write", "test", "ship", "celebrate", "rest"]
last = tasks.pop()
first = tasks.pop(0)
print(last, first, tasks)
tasks.remove("test")
print(tasks, len(tasks))
print(tasks.index("ship"))
Output
rest plan ['write', 'test', 'ship', 'celebrate']
['write', 'ship', 'celebrate'] 3
1
  • pop() removes and returns the last item. pop(i) does the same for position i.
  • remove(x) deletes the first item equal to x.
  • index(x) finds the position of the first match. x in items just asks whether it is there.
  • items.clear() empties the list.

remove and index raise ValueError when the item is missing, so test with in first:

tasks = ["write"]
tasks.remove("nap")
Output
ValueError: list.remove(x): x not in list

Notice that these methods change the list in place and mostly return None, with pop as the exception. Writing tasks = tasks.append("x") replaces your list with None.

Lists can also be joined with + and repeated with *, which build new lists:

list_operators.py
print([1, 2] + [3])
print([1, 2] * 2)
Output
[1, 2, 3]
[1, 2, 1, 2]

Summing up a list

The built-in functions len, min, max and sum accept a whole list:

list_stats.py
scores = [88, 95, 70, 91]
print(len(scores), min(scores), max(scores), sum(scores))
print(sum(scores) / len(scores))
Output
4 70 95 344
86.0

sort versus sorted

sorted(items) builds a new sorted list and leaves the original alone. items.sort() sorts in place and returns None. Pick sorted when you still need the original order, and sort when you do not.

list_sorted.py
scores = [88, 95, 70, 91]
ranked = sorted(scores, reverse=True)
print(ranked, scores)
result = scores.sort()
print(result, scores)
Output
[95, 91, 88, 70] [88, 95, 70, 91]
None [70, 88, 91, 95]

Both accept reverse=True and a key= function that says what to sort by: key=len orders words by length. The sort is stable, so items with equal keys keep their original relative order (pear stays ahead of kiwi).

list_sort_key.py
words = ["pear", "fig", "banana", "kiwi"]
words.sort(key=len)
print(words)
Output
['fig', 'pear', 'kiwi', 'banana']

Aliasing and copies

b = a does not copy a list. It gives the same list a second name, so a change through b shows up through a. The expression a is b confirms that both names point at one object.

list_alias.py
a = [1, 2, 3]
b = a
b.append(4)
print(a, b, a is b)
Output
[1, 2, 3, 4] [1, 2, 3, 4] True

To get an independent list, call a.copy() or write a[:], which the slicing lesson covers.

list_copy.py
a = [1, 2, 3, 4]
c = a.copy()
c.append(5)
print(a, c, a is c)
Output
[1, 2, 3, 4] [1, 2, 3, 4, 5] False

Those copies are shallow. They copy the outer list but share the inner objects. Change shallow[0][0] and the original grid changes too, because both outer lists hold the same inner list at position 0.

list_shallow.py
grid = [[0, 0], [0, 0]]
shallow = grid.copy()
shallow[0][0] = 9
print(grid)
Output
[[9, 0], [0, 0]]

The same sharing explains the classic [[0] * 2] * 2 mistake: the outer * repeats a reference to one inner list. Build a grid with a loop, and every row is its own list:

list_grid.py
wrong = [[0] * 2] * 2
wrong[0][0] = 9
print(wrong)
right = []
for _ in range(2):
right.append([0] * 2)
right[0][0] = 9
print(right)
Output
[[9, 0], [9, 0]]
[[9, 0], [0, 0]]

The name _ is a convention for “a loop variable I do not use”. The shared fundamentals track covers shallow versus deep copies in its memory and references lesson.

Common pitfalls

  • Assigning the result of an in-place method. items = items.sort() leaves items as None. Call items.sort() on its own line, or use sorted.
  • Thinking b = a copies. It adds a name. Use a.copy() when you need a second list.
  • Repeating a list of lists with *. [[0] * 2] * 2 makes two references to one row. Build the rows in a loop.
  • Removing items while looping. Deleting from a list you are iterating over skips elements. Loop over a copy, or build a new list.
  • IndexError on empty or short lists. [][0] and [1, 2][5] both raise it. Check if items: or len(items) first.
  • Using remove on a missing item. It raises ValueError. Test with in first.
ExerciseFix the sort bug

This program should print the words in alphabetical order, but it prints None. Fix it in a single line.

Input: none. Expected output: ['fig', 'kiwi', 'pear'].

words = ["pear", "fig", "kiwi"]
words = words.sort()
print(words)
Output
None
Show solution
sort_fixed.py
words = ["pear", "fig", "kiwi"]
words.sort()
print(words)
Output
['fig', 'kiwi', 'pear']

sort() changes the list in place and returns None, so assigning its result threw the list away. Calling it on its own line keeps the sorted list. words = sorted(words) also works.

ExerciseTodo list

Start with the list ["buy milk", "call Ada", "water plants"]. Print it as a numbered list starting at 1. Then try to remove "call Ada" and print removed: True if it was there and removed: False if it was not, and do the same for "fly to the moon". Print the numbered list again. Finally clear the list, and print Nothing to do. if it is empty.

Input: none. Expected output:

1. buy milk
2. call Ada
3. water plants
removed: True
removed: False
1. buy milk
2. water plants
Nothing to do.
Show solution
todo_list.py
todos = ["buy milk", "call Ada", "water plants"]
for number, item in enumerate(todos, start=1):
print(f"{number}. {item}")
for name in ["call Ada", "fly to the moon"]:
if name in todos:
todos.remove(name)
print("removed: True")
else:
print("removed: False")
for number, item in enumerate(todos, start=1):
print(f"{number}. {item}")
todos.clear()
if not todos:
print("Nothing to do.")
Output
1. buy milk
2. call Ada
3. water plants
removed: True
removed: False
1. buy milk
2. water plants
Nothing to do.

Testing with in before remove avoids the ValueError. enumerate(..., start=1) gives people-friendly numbering, and an empty list is falsy, so not todos is the test for “nothing left”.

ExerciseTop three, original untouched

Given scores = [72, 95, 88, 64, 95, 81], print the three highest scores, highest first, and then print the original list to show it has not changed. Do not modify scores.

Input: none. Expected output: [95, 95, 88] then [72, 95, 88, 64, 95, 81].

Show solution
top_three.py
scores = [72, 95, 88, 64, 95, 81]
print(sorted(scores, reverse=True)[:3])
print(scores)
Output
[95, 95, 88]
[72, 95, 88, 64, 95, 81]

sorted returns a new list, so scores is never modified. The slice [:3] takes the first three items and stops quietly at the end of a shorter list instead of raising an error.

Summary

  • Lists are ordered and mutable. append, extend, insert, pop and remove change them in place.
  • sorted(x) returns a new list. x.sort() changes x and returns None.
  • b = a shares one list. copy() makes a shallow copy, and nested lists are still shared.

Next: tuples and unpacking.