SchoolWhool
Quizzes and saved progress, freeSign in freeSign in

Python 3.1Free video21 minLesson 1 of 6 in Module 3

Lists

Store ordered, changeable collections and slice them.

Free sample. Everything in this module opens with a free account.

Watch or read the lesson: Lists

01List access without a loop: index, slice and max

Remember the step counts from the for loops lesson? There were seven numbers, one for each day of the week, and you used a loop to go through them and calculate the total.

But often you want something more specific out of a list. What if you only want the step count for Monday? Or the last three days of the week? Or the day with the highest step count? Do you really need to write a loop every time?

Not at all. Here are three short examples. The first gives the step count for the first day:

week = [8200, 10450, 6300, 12000, 9100, 7400, 11050]
week[0]
8200

The second gives the step counts for the last three days:

week[-3:]
[9100, 7400, 11050]

And the third finds the highest step count of the week:

max(week)
12000

Notice that none of these examples uses a loop. So how do they work? That is what this lesson explores. It also looks at something that can cause some very confusing bugs: what happens when two different variable names refer to the same list.

02Indexing, and negative indices

Start with the basics. A list is a collection of values stored in a particular order, and each value inside the list is called an elementKey termOne value stored in a list..

The elements of a list do not all have to be the same type. This is a perfectly valid list, holding a float, a str and a bool:

mixed = [12.5, "Pasta", True]
mixed[1]
'Pasta'

The elements have an order, so each one has a position. In Python, that position is called an indexKey termAn element's position, counting from zero. A negative index counts from the end, from -1..

Here is the first thing to remember: Python starts counting indices from zero, not one. So the first element is at index 0, the second element is at index 1, and the third element is at index 2.

To access an element, you write the name of the list, followed by its index inside square brackets. week[0] gives 8200, as you saw above. That is the first day's step count. And:

week[3]
12000

That is the fourth day. Why the fourth? Because the count starts at zero, index 3 refers to the fourth element.

The last index

Now think about the size of the list. There are seven step counts, and you can confirm that with the len() function:

len(week)
7

So what are the valid indices? They start at 0 and go up to 6. That means week[6] gives the last value:

week[6]
11050
Predict first

Here is a quick question. What do you think happens if you try to access week[7]? Will Python give you the last value? Will it return None? Or will something else happen? Think it through before reading on.

I have a guess. Show meHide the answer
week[7]
IndexError: list index out of range

You get an IndexError. Python is telling you that the list index is out of range. Although the list contains seven elements, there is no element at index 7. The last valid index is 6.

This is a very common mistake when you first work with lists. The length tells you how many elements there are, but because indexing starts at zero, the last index is one less than the length.

Counting from the end

Now say you want the last element of a list. You could use index 6 in this example. But what if you do not know how many elements the list contains? Or what if the list keeps growing?

Python gives you another way: negative indexing. Instead of counting from the beginning, you count backward from the end. week[-1] gives the last element:

week[-1]
11050

And week[-2] gives the second-to-last element:

week[-2]
7400

You can keep going backward. For this seven-element list, week[-7] takes you all the way back to the first value:

week[-7]
8200

So each element has two indices: one counting forward from 0, and another counting backward from -1. Negative indices are especially convenient when you want the last few elements without calculating the length yourself.

03Slicing

So far you have accessed one element at a time. But what if you want several elements together? Say you have the temperatures for a ten-day period, and you want the ones for a few days in the middle.

That is where slicing comes in. A sliceKey termA range of elements, from a start up to but not including a stop, with an optional step: temps[2:5], temps[::-1]. Slicing a list creates a new outer list; slicing a tuple or string returns the same type, possibly reusing an unchanged immutable object. selects a range of elements from a sequence. Here is a list called temps, holding ten daily temperatures, and a slice of it:

temps = [18.2, 19.5, 21.0, 22.4, 20.1, 17.8, 19.9, 23.3, 24.0, 22.6]
temps[2:5]
[21.0, 22.4, 20.1]

Notice the colon between the two numbers. The first number tells Python where to start, and the second tells it where to stop. But there is one important detail: the stop index is excluded. The slice starts at index 2 and stops just before index 5, so you get the elements at indices 2, 3 and 4.

That is why you get three temperatures. 5 - 2 is 3, and because the stop index is excluded, that is exactly how many elements you get. This is the same start-inclusive, stop-exclusive behavior you saw with range().

There is another important detail about slicing: a slice creates a new list. It does not remove those elements from the original list. After taking the slice, temps still contains all ten temperatures.

Leaving out the start or the stop

You do not always need to provide both the start and the stop. Here the starting index is left out, so Python starts from the beginning and gives you the first three elements:

temps[:3]
[18.2, 19.5, 21.0]

What if you leave out the stop instead? temps[-7:] means: start seven elements from the end, and continue all the way to the end. So you get the last seven temperatures:

temps[-7:]
[22.4, 20.1, 17.8, 19.9, 23.3, 24.0, 22.6]

Since the list currently has ten elements, you could also write this, and get exactly the same seven values:

temps[3:]
[22.4, 20.1, 17.8, 19.9, 23.3, 24.0, 22.6]

So why would you use -7? Because temps[3:] always starts at index 3, but temps[-7:] always gives you the last seven elements, as long as the list has at least seven. If you add more temperatures later, the negative-index version still selects the most recent seven. That is often exactly what you want.

A step

Slices have one more option: a step. Just like with range(), you can provide a third value to control how Python moves through the sequence. Here the start and stop are left out, but there is a step of 2, so Python starts at the beginning and takes every second element:

temps[::2]
[18.2, 21.0, 20.1, 19.9, 24.0]

What if you use a negative step? A step of -1 moves backward through the list, so this gives you all the temperatures in reverse order:

temps[::-1]
[22.6, 24.0, 23.3, 19.9, 17.8, 20.1, 22.4, 21.0, 19.5, 18.2]

The last element comes first, and the first element comes last. And remember, the result is a new list. The original temps list has not changed.

04Changing a list: append, insert, remove and pop

Now that you know how to read elements from a list, here is how to change one. Imagine you are building a simple shopping cart, and you start with three items: milk, bread and apples.

Suppose you want to add eggs to the cart. Lists have a method called append() for that:

cart = ["milk", "bread", "apples"]
cart.append("eggs")
cart
['milk', 'bread', 'apples', 'eggs']

First, the terminology. A methodKey termA function that belongs to an object, called with a dot: cart.append(...), review.lower(). is a function associated with an object, and you call it using a dot after the object's name. So here you are calling the append() method on the cart list. append() adds a new element to the end of the list, and now the cart has four items: milk, bread, apples and eggs.

insert

What if you do not want to add something at the end, but at a particular position? For that you can use insert(). This inserts coffee at position 1:

cart.insert(1, "coffee")
cart
['milk', 'coffee', 'bread', 'apples', 'eggs']

The first argument is the index, and the second is the item you want to insert. Python places 'coffee' at index 1, and the existing items from that position onward move one place to the right.

remove

What about removing something? For that you can use remove(). Suppose you no longer want bread:

cart.remove("bread")
cart
['milk', 'coffee', 'apples', 'eggs']

Python removes the first element equal to "bread", and you are left with milk, coffee, apples and eggs.

But what if you try to remove something that is not in the list? Try removing tea:

cart.remove("tea")
ValueError: list.remove(x): x not in list

You get a ValueError, because Python could not find that value. So remember: remove() searches for a matching element, and if it does not find one, it raises an error.

Assigning to an index, and pop

Sometimes you do not want to add or remove an item. You just want to replace one. Suppose you want oat milk instead of regular milk. You can simply assign a new value to the existing index:

cart[0] = "oat milk"
cart
['oat milk', 'coffee', 'apples', 'eggs']

The first element has changed, and the rest of the cart stays the same.

There is another useful method called pop(). By default, it removes the last element from a list. But unlike remove(), it also returns the item it removed:

cart.pop()
'eggs'

Python removed eggs, and the expression returned 'eggs'. That means you could store the removed item in another variable if you wanted to. After the operation, the cart contains oat milk, coffee and apples:

cart
['oat milk', 'coffee', 'apples']

What these methods return

You might notice that when you call append() by itself in a notebook cell, no list is displayed. Why? Because append() does not return a new list. It changes the existing list, and its return value is None. The same is true for insert() and remove(): they modify the list and return None.

pop() is different. It modifies the list and returns the element it removed.

Lists are mutable

This brings you to an important property of Python lists: they are mutableKey termA type whose objects can be changed after they are created; a list is mutable, and so are sets and dictionaries.. Mutable simply means that an object can be changed after it is created. So when you append, insert, remove or replace an element, you are changing the existing list object. You are not necessarily creating a new list.

Compare that with integers and floats. Those are immutable: you cannot change an integer or float object itself. When you update a variable holding a number, Python binds that name to the result of the calculation. But with a list, methods like append() can change the object that the name already refers to. That distinction becomes very important later in this lesson.

05Built-in functions: sum, min and max

So far you have learned how to access individual elements, select ranges and modify lists. Python also has several built-in functions that answer questions about an entire list.

Go back to the weekly step counts. In the for loops lesson, you used an accumulator to calculate the total number of steps, and got 64500. Python has a built-in function called sum():

sum(week)
64500

You get the same result, with no need to write the loop yourself.

What if you want the smallest step count? You can use min():

min(week)
6300

And for the highest step count, you use max():

max(week)
12000

06sort and sorted

Now for sorting. Suppose you want to arrange the weekly step counts from lowest to highest. Python gives you two common ways to do this, and there is an important difference between them.

The first is the built-in function sorted():

sorted(week)
[6300, 7400, 8200, 9100, 10450, 11050, 12000]

Python gives you a new list with the values arranged in ascending order, starting at 6,300 and ending at 12,000. But here is the important part: the original week list has not changed. The days are still in their original order:

week
[8200, 10450, 6300, 12000, 9100, 7400, 11050]

If you want the highest values first, you can pass reverse=True:

sorted(week, reverse=True)
[12000, 11050, 10450, 9100, 8200, 7400, 6300]

Now the new list starts with 12,000 and goes down to 6,300.

The sort method

The other way to sort a list is the sort() method:

week.sort()
week
[6300, 7400, 8200, 9100, 10450, 11050, 12000]

At first this might look like the same thing, and yes, it arranges the values from lowest to highest. But there is a crucial difference: sort() changes the original list itself. After you call it, week is no longer in the order of the days. It is now ordered by step count.

And unlike sorted(), the sort() method does not return the sorted list. It returns None:

print(week.sort())
None

You see None, even though the list itself has been sorted.

The None mistake

Predict first

Why does that matter? Because it leads to a very common mistake. Say you have a list called prices, and you write prices = prices.sort(). What do you think prices refers to afterwards? Think it through before reading on.

I have a guess. Show meHide the answer
prices = [12.50, 8.25, 6.00, 9.75]
prices = prices.sort()
print(prices)
None

Python first calls the sort() method, which sorts the original list in place. But then the method returns None, and the assignment stores that return value in prices. So now prices refers to None, not the sorted list.

Usually you find out on the next line that uses it:

prices.append(4.50)
AttributeError: 'NoneType' object has no attribute 'append'

You get an AttributeError, because None does not have an append method.

Which one to use

So here is the rule:

  • Use sorted() when you want a new sorted list while keeping the original unchanged.
  • Use .sort() when you want to sort the existing list itself.
  • Do not assign the return value of .sort() back to your list variable.

07Aliasing: two names bound to one list

Now for one of the most important concepts in this lesson. It can cause very confusing bugs if you do not understand how Python handles objects.

Go back to the shopping cart, which is currently ['oat milk', 'coffee', 'apples']. Suppose you want to make a backup before changing it, so you write backup = cart. That looks completely reasonable. You have the original cart, and you have created another variable called backup, so you might expect them to be two separate lists.

Predict first

To test that, after creating backup, add bread to the cart with cart.append("bread"). Now, what do you think backup shows? Will it show the cart before you added bread, or will bread appear there too? Think it through before reading on.

I have a guess. Show meHide the answer
backup = cart
cart.append("bread")
backup
['oat milk', 'coffee', 'apples', 'bread']

Surprisingly, backup also contains bread. Both variables show exactly the same items. Why? Because when you wrote backup = cart, you did not create a new list. You simply created another name referring to the same list object.

You can verify that with the is operator:

backup is cart
True

Python returns True, because both names refer to the exact same object.

Rebinding a number

Remember the variables lesson? Assigning one variable to another does not copy the object. But there is an important difference between lists and numbers. Here is a numeric example again. Suppose total refers to the float 24.75, and you write previous_total = total. Now both names refer to the same float object. Then you assign 26.25 to total:

total = 24.75
previous_total = total
total = 26.25
previous_total
24.75

previous_total still refers to 24.75. Assigning a new value to total does not change the original float object. It simply rebinds the name total to another object. And since floats are immutable, there is no way to modify that float in place.

Mutation through an alias

Now compare that with the list. After backup = cart, both names referred to the same list. But then you called cart.append("bread"), and append does not reassign cart to a different list. It changes the existing list object. So when you look through either name, you see the updated list, because there is only one list and both names refer to it.

This is called aliasing. An aliasKey termA second name bound to the same object: after backup = cart, backup is an alias of cart. is simply another name for the same object. And changing an object in place is called mutationKey termChanging an object in place, which every alias sees: append, remove, sort, assigning to an index..

So here is the distinction to remember:

  • Assignment binds a name to an object.
  • Reassignment changes which object a name refers to.
  • Mutation changes the object itself.

If several names refer to the same mutable object, they all see the change. This is especially important when you work with lists, because you might think you have created a backup when you have actually just created another name for the original list.

08Shallow copies: separate outer lists and shared inner objects

So how do you make a real copy? Lists have a method called copy(). With backup = cart.copy(), Python creates a new list containing the same elements, and backup refers to that new list. So there are two separate list objects:

backup = cart.copy()
backup is cart
False

This time Python returns False, because they are different objects. But what if you compare them using ==?

backup == cart
True

That returns True, because the lists contain equal elements. This connects directly to what you learned about identity and equality: == checks whether the values are equal, while is checks whether two names refer to the same object.

Now add tea to the cart, and print both lists:

cart.append("tea")
print(cart)
print(backup)
['oat milk', 'coffee', 'apples', 'bread', 'tea']
['oat milk', 'coffee', 'apples', 'bread']

This time, tea appears only in cart. backup stays unchanged.

Shallow copies

There is an important term you should know here. The copies copy() makes are called shallow copies. A shallow copy creates a new outer list, but it does not recursively copy the objects inside it. The new list holds the same element objects as the original.

For the shopping cart, where the elements are strings, that is fine, because strings are immutable. But if a list contains other mutable objects, like nested lists, the original and the copy can still share those inner objects. You will explore that distinction in more detail when you work with nested collections.

For now, remember that copying a list gives you a new outer list, while simple assignment does not.

09Recap and challenge: indexing, slicing, list methods, aliasing and copies

This lesson started with the weekly step counts and showed how to access specific values without writing a loop. You used indices to read individual elements, and slices to select several elements at once. You saw how negative indices count backward from the end, and how a slice can use a step to skip elements or reverse their order.

Then you explored list methods like append, insert, remove and pop, and learned that lists are mutable, meaning you can change them in place. You used built-in functions like sum, min, max and sorted to work with entire lists, and learned the difference between sorted and sort.

Finally, you explored what happens when two names refer to the same list, and saw why assigning one list variable to another does not create a copy, while copy() gives you a new outer list.

Your challenge

Go back to the week of step counts from the beginning of this lesson:

week = [8200, 10450, 6300, 12000, 9100, 7400, 11050]
  1. Find the three highest step counts of the week. Do not write a loop: use sorted() and slicing to get the result.
  2. Calculate the average step count for the last three days, and display it with two decimal places.

There is one important condition. After your program runs, the original week list should still be in the same order, so you do not lose which step count belongs to which day. Think carefully about whether you should use sorted() or .sort(). Once you have finished, try explaining to yourself why your original list has not changed.

The next lesson looks at another way to group values together. Unlike a list, this type is immutable. It is called a tuple.