Skip to content

Lists

Goal: store many values in one variable, change them, and work through them one at a time.

Why this matters

Imagine a to-do app. So far, every value you have stored needed its own variable: task1, task2, task3. That breaks the moment the user wants a fourth task, and there is no way to say "print every task" without writing a print for each variable. A list is a single value that holds any number of other values, in order. Nearly every useful program keeps a list of something: tasks, scores, names, files, messages.

Creating a list

Write the values between square brackets, separated by commas:

create_list.py
todo = ["buy milk", "call mum", "finish homework"]
scores = [82, 95, 77, 60]
mixed = ["Ada", 36, True]
empty = []

print(todo)
print(scores)
print(mixed)
print(empty)
print(len(todo))
['buy milk', 'call mum', 'finish homework']
[82, 95, 77, 60]
['Ada', 36, True]
[]
3

A list can hold strings, numbers, booleans, or a mix, though most lists hold one kind of thing. [] is an empty list, which is a normal starting point when you plan to add items later. len() works on lists exactly as it does on strings, giving the number of items.

Printing a list shows the whole thing, brackets and all, with strings in quotes. That is Python showing you the exact contents, which is handy while you are learning. Later in the lesson you will print items one per line instead.

Picking out items

Every item has an index, counting from 0, and square brackets pull one out. If this looks familiar, it is because strings work the same way, and so do slices and negative indexes:

list_indexing.py
todo = ["buy milk", "call mum", "finish homework", "water plants"]

print(todo[0])
print(todo[2])
print(todo[-1])
print(todo[1:3])
print(todo[:2])
print(todo[2:])
buy milk
finish homework
water plants
['call mum', 'finish homework']
['buy milk', 'call mum']
['finish homework', 'water plants']
 "buy milk"  "call mum"  "finish homework"  "water plants"
      0           1              2                3
     -4          -3             -2               -1
  • todo[0] is the first item, todo[-1] the last.
  • todo[1:3] is a slice: items from index 1 up to but not including index 3. A slice of a list is a new, smaller list.
  • Everything you learned about string indexes carries over, including IndexError when you go past the end.

Changing a list

Here is where lists differ from strings. A string is immutable: .upper() hands you a new string and leaves the old one alone. A list is mutable, which means you can change it in place. The simplest change is replacing one item:

change_item.py
todo = ["buy milk", "call mum", "finish homework"]
print(todo)

todo[0] = "buy oat milk"
print(todo)
['buy milk', 'call mum', 'finish homework']
['buy oat milk', 'call mum', 'finish homework']

todo[0] = ... on the left of an equals sign means put this value at index 0. Try that with a string and you get an error, which is listed under Common mistakes below.

Adding items

Two methods add items. .append() puts a value at the end, and .insert() puts it at any index you choose, shifting the items after it along by one:

append_insert.py
todo = ["buy milk", "call mum"]

todo.append("finish homework")
print(todo)

todo.insert(0, "wake up")
print(todo)
['buy milk', 'call mum', 'finish homework']
['wake up', 'buy milk', 'call mum', 'finish homework']

Notice there is no todo = todo.append(...). String methods return a new string that you must store. List methods like .append() change the list itself and return nothing. If you write todo = todo.append("x"), todo becomes None and the list is gone. This trips up almost every beginner once.

.append() is by far the most used list method. You will write it constantly.

Removing items

.remove() takes a value and deletes the first item that matches it. .pop() takes an index, deletes the item there, and gives it back to you so you can use it. With no index, .pop() removes the last item:

remove_pop.py
todo = ["wake up", "buy milk", "call mum", "finish homework"]

todo.remove("call mum")
print(todo)

done = todo.pop(0)
print(f"Finished: {done}")
print(todo)

last = todo.pop()
print(f"Finished: {last}")
print(todo)
['wake up', 'buy milk', 'finish homework']
Finished: wake up
['buy milk', 'finish homework']
Finished: finish homework
['buy milk']

Use .remove() when you know what to delete, and .pop() when you know where it is or you need the value afterwards.

Sorting

.sort() puts the items in order, in place. Numbers sort numerically and strings alphabetically. .reverse() flips the order:

sort_reverse.py
scores = [82, 95, 77, 60]
names = ["Grace", "Ada", "Linus"]

scores.sort()
print(scores)

names.sort()
print(names)

names.reverse()
print(names)
[60, 77, 82, 95]
['Ada', 'Grace', 'Linus']
['Linus', 'Grace', 'Ada']

Like .append(), both change the list and return nothing, so do not store the result. Sorting a list that mixes numbers and strings fails, because Python does not know whether 3 comes before "Ada".

Looping over a list

A for loop visits each item in turn, just as it visits each character of a string. This is how you print a list nicely:

loop_list.py
todo = ["buy milk", "call mum", "finish homework"]

for task in todo:
    print(f"- {task}")

print(f"{len(todo)} tasks in total.")
- buy milk
- call mum
- finish homework
3 tasks in total.

Name the loop variable after one item, task, and the list after many, todo or tasks. Then the loop reads as English: for each task in the to-do list.

Adding up a list

The accumulator pattern from Module 3 works on lists. Because adding up is so common, Python also has sum() built in, along with max() and min():

list_total.py
scores = [82, 95, 77, 60]

total = 0
for score in scores:
    total += score
print(f"Total by hand: {total}")

print(f"Total with sum(): {sum(scores)}")
print(f"Highest: {max(scores)}")
print(f"Lowest: {min(scores)}")
print(f"Average: {sum(scores) / len(scores)}")
Total by hand: 314
Total with sum(): 314
Highest: 95
Lowest: 60
Average: 78.5

Use the built-in functions when they fit. The hand-written loop is still worth knowing, because the moment you want something they do not do, such as adding only the scores above 70, the loop is what you reach for.

Checking whether something is in a list

in and not in work on lists too. For a string they look for a piece of text inside it. For a list they look for a whole item that is equal:

list_in.py
todo = ["buy milk", "call mum", "finish homework"]

print("call mum" in todo)
print("go running" in todo)

if "buy milk" in todo:
    print("Don't forget the milk!")
True
False
Don't forget the milk!

"milk" in todo would be False here, because no item is exactly "milk". The item is "buy milk".

Building a list from input

Put together [], while True, and .append() and you have the shape of every program that collects things from the user:

build_list.py
shopping = []

while True:
    item = input("Add an item, or done to finish: ")
    if item == "done":
        break
    shopping.append(item)

print(f"You need {len(shopping)} things:")
for item in shopping:
    print(f"- {item}")
Add an item, or done to finish: eggs
Add an item, or done to finish: bread
Add an item, or done to finish: apples
Add an item, or done to finish: done
You need 3 things:
- eggs
- bread
- apples

Start with an empty list, add to it inside the loop, use it after the loop. This is the accumulator pattern again, with a list instead of a number.

Try it

Make a list of your five favourite foods. Print the first and the last. Add a sixth with .append(), remove one with .remove(), sort the list, then print it one item per line with a for loop.

Common mistakes

TypeError: 'str' object does not support item assignment

You wrote word[0] = "J" on a string. Strings cannot be changed in place. Build a new string instead, for example word = "J" + word[1:].

AttributeError: 'NoneType' object has no attribute 'append'

Somewhere you wrote todo = todo.append(...) or todo = todo.sort(). Those methods return None, so the list was replaced with nothing. Call them on their own line: todo.append(...).

IndexError: list index out of range

The index does not exist. The last index of a list is len(todo) - 1, and an empty list has no valid index at all. Check the length before indexing, or use a for loop, which never goes out of range.

ValueError: list.remove(x): x not in list

.remove() was given a value that is not in the list. Check with if item in todo: first.

The list prints with brackets and quotes

That is what print(todo) does. To show items nicely, loop over them and print each one.

Exercises

  1. Guest list. Start with a list of three names. Ask the user for one more, add it to the end, then print the list and how many guests are coming.
  2. Statistics. Ask the user for whole numbers until they type done, skipping anything that is not a number. Then print the count, total, average, highest, and lowest. Handle the case where no numbers were entered.
  3. No duplicates. Ask for names until the user types done. Add each name to a list, but if the name is already there, say so and do not add it again. Print the list at the end.
  4. Longest name. Given the list ["Ada", "Grace", "Linus", "Guido", "Margaret"], find and print the longest name using a loop, without using max().
Solution 1
guests = ["Ada", "Grace", "Linus"]

new_guest = input("Who else is coming? ")
guests.append(new_guest)

print(guests)
print(f"{len(guests)} guests are coming.")
Solution 2
numbers = []

while True:
    text = input("Enter a number, or done to finish: ")
    if text == "done":
        break
    if not text.isdigit():
        print("That is not a whole number, skipping.")
        continue
    numbers.append(int(text))

if len(numbers) == 0:
    print("You did not enter any numbers.")
else:
    print(f"Count: {len(numbers)}")
    print(f"Total: {sum(numbers)}")
    print(f"Average: {round(sum(numbers) / len(numbers), 1)}")
    print(f"Highest: {max(numbers)}")
    print(f"Lowest: {min(numbers)}")

Without the if len(numbers) == 0: check, an empty list would crash the program on the average line, because dividing by zero is an error. The round() from Module 1 keeps the average tidy.

Solution 3
names = []

while True:
    name = input("Enter a name, or done to finish: ")
    if name == "done":
        break
    if name in names:
        print(f"{name} is already on the list.")
        continue
    names.append(name)

print(names)
Solution 4
names = ["Ada", "Grace", "Linus", "Guido", "Margaret"]

longest = names[0]
for name in names:
    if len(name) > len(longest):
        longest = name

print(f"The longest name is {longest} with {len(longest)} letters.")

This is the accumulator pattern with a twist: instead of adding to a total, longest is replaced whenever the loop finds something better. Starting with names[0] rather than an empty string means the answer is always a real name.

Summary

  • A list holds any number of values in order: todo = ["a", "b", "c"]. [] is an empty list.
  • Indexes, slices, negative indexes, and len() all work the same as for strings.
  • Lists are mutable. todo[0] = x replaces an item, .append(), .insert(), .remove(), .pop(), and .sort() change the list in place.
  • List methods return None. Never write todo = todo.append(x).
  • for item in todo: visits every item. sum(), min(), max(), and in all work on lists.

Next: Dictionaries