Readings: lists
Contents
5.1. Readings: lists#
In Chapter 3, we first faced the need of encapsulating many values in the same variable when introducing the fermented_food
and fermented_drinks
variables that had a collection of values. Both variables were examples of lists.
5.1.1. Lists#
The syntax for declaring a list is:
list_name = [item1, item2, item3,...]
Anything enclosed in square brackets []
and separated by ,
will be a list. With the above piece of code you create a list and assign it to the list_name
variable. Some examples of lists would be:
natural_numbers = [1, 2, 3, 4, 5]
rational_numbers = [1.1, 2.5, 3.4]
letters = ['p', 'y', 't', 'h', 'o', 'n']
booleans = [True, False, False, False, True]
The lists in the examples above contain the same data types, which means that they are homogenous. However, in Python it is possible/allowed for a list to contain different data types. These lists are called heterogenous. For example:
mixed_list = [1, 'p', 1.1, True]
An in previous chapters, we can use the print()
function to print the contents of a list:
print(natural_numbers)
[1, 2, 3, 4, 5]
5.1.1.1. Number of elements in a list#
In most cases we do not know how many elements there are in a list. It is possible that during the execution of the program the number of elements in a list will change, e.g., if we add/remove elements to/from a list, accept a list created from user inputs, etc. In these situations, if we would like to iterate over the elements of a list, it would be impossible to hard-code the number of iterations like we did so far in the previous chapters. That’s why the len()
function is very useful. It gets a list as an argument and outputs the length of that list, in other words: the number of elements that the list has. For example:
len(mixed_list)
4
5.1.1.2. Accessing elements of the list#
Lists in Python use 0-based indexing. This means that the first element is at position 0, the second element at position 1 and so on. Fig. 5.1 illustrates this idea.

Fig. 5.1 Indexing in Python#
Hint
Since Python uses 0-based indexing this means that in a list with n
elements, the last element will be in position n-1
.
In order to access element t
in the letters
list, in the example above we would write:
letters[2]
't'
Although character t
is the third element in the list, in order to access it we should use index 2
since lists use 0-based indices.
In Python we can also access elements of a list using negative indices. In this setting, the last element of the list is at position -1, the second from the last is at position -2 and so on. Fig. 5.2 illustrates the idea:

Fig. 5.2 Negative Indexing in Python#
letters[-4] #accessing element 't'
't'
Caution
If we try to access an element at a position that does not exist then we will get an IndexError
. E.g: in the letters
list above if we try to access elements at position 9 or -8 then we will get an IndexError
since the letters
list does not contain 10 or 8 elements respectively.
letters[9]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Cell In[7], line 1
----> 1 letters[9]
IndexError: list index out of range
We can use indices in a loop to access elements of a list as well, just as we did in Nested Loops in Chapter 3. For example, to print each element of the letters
list using a for
loop we would do:
for i in range(0, len(letters)):
print(letters[i], end=' ')
p y t h o n
Similarly, using a while
loop we could write:
j = 0
while j < len(letters):
print(letters[j], end=' ')
j+=1
p y t h o n
Important
Here we used the function len(letters)
to determine the number of elements of the letters
list inside the range()
function. So the range()
function will produce the sequence of numbers {0, 1, 2,..., len(letters)-1}
that corresponds to the indices needed to access the elements of the list. Thus we do not need to hardcode the number of elements that a list will have since we can access it via the len()
function, as explained in the previous section.
We can also select a range of values in a list by placing a range inside the square brackets with the following syntax:
list_name[start_pos:end_pos]
where end_pos
is non-inclusive. This is called slicing.
letters[1:4] # print letters of positions 1,2,3
['y', 't', 'h']
There are also variations of range selection in lists. For example, if we write:
list_name[start_pos: ]
- this will select all elements fromstart_pos
until the end of the list.list_name[ :end_pos]
- this will select all elements from the beginning until theend_pos
non-inclusive.list_name[ : ]
- this will select all elements of the list (equivalent to callinglist_name
).
5.1.1.3. Adding/removing elements to/from a list#
Lists are mutable data-types. This means that we can modify a list and its objects by adding, deleting, or modifying certain list elements.
5.1.1.3.1. Modifying elements of a list#
In order to modify a list element we access it using its index and then we set it to the new value. For example, suppose we want to change the second element of the natural_numbers
list from 2
to 10
. Then we would write:
print('List of natural numbers before changes:', natural_numbers)
natural_numbers[1] = 10
print('List of natural numbers after changes:', natural_numbers)
List of natural numbers before changes: [1, 2, 3, 4, 5]
List of natural numbers after changes: [1, 10, 3, 4, 5]
5.1.1.3.2. Adding elements to a list#
In order to add elements to a list we can use the +=
operator. We can add a single value or many values in the form of iterable (list, tuple, etc).
natural_numbers += [20]
print(natural_numbers)
natural_numbers += [30, 40]
print(natural_numbers)
[1, 10, 3, 4, 5, 20]
[1, 10, 3, 4, 5, 20, 30, 40]
Caution
Be careful if you try to add a single value to a list using the +=
operator. It will raise a TypeError
since a single value is not an iterable (collection of values).
natural_numbers += 20
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[5], line 1
----> 1 natural_numbers += 20
TypeError: 'int' object is not iterable
There are other ways to add elements to a list, using different built-in methods.
We can use the append()
method to add a single element to a list.
rational_numbers.append(4.7)
print(rational_numbers)
[1.1, 2.5, 3.4, 4.7]
We can use the insert()
method to add an element to a list at a specific position. Its syntax is:
list_name.insert(position, element)
print('natural_numbers before adding 50:', natural_numbers)
natural_numbers.insert(3, 50)
print('natural_numbers after adding 50:', natural_numbers)
natural_numbers before adding 50: [1, 10, 3, 4, 5, 20, 30, 40]
natural_numbers after adding 50: [1, 10, 3, 50, 4, 5, 20, 30, 40]
In order to add elements of any type of collection (tuples, sets and dictionaries) to a list we can use the extend()
method as well. The syntax is:
list_name.extend(collection_to_be_added)
For example, we can append the natural_numbers
modified list to the rational_numbers
list using the extend()
method.
print('rational_numbers before extending to natural_numbers:', rational_numbers)
rational_numbers.extend(natural_numbers)
print('rational_numbers after extending to natural_numbers:', rational_numbers)
rational_numbers before extending to natural_numbers: [1.1, 2.5, 3.4]
rational_numbers after extending to natural_numbers: [1.1, 2.5, 3.4, 1, 10, 3, 50, 4, 5, 20, 30, 40]
5.1.1.3.3. Removing elements from a list#
There are two methods to remove elements from a list: remove()
and pop()
. The remove()
method allows us to specify an element from the list to remove.
print('natural_numbers before removing 50:', natural_numbers)
natural_numbers.remove(50)
print('natural_numbers after removing 50:', natural_numbers)
natural_numbers before removing 50: [1, 10, 3, 50, 4, 5, 20, 30, 40]
natural_numbers after removing 50: [1, 10, 3, 4, 5, 20, 30, 40]
The pop()
method has two versions: one without any arguments and one with one argument. If we call pop()
without any arguments then it will remove the last element from the list. If we call pop(index)
it will remove the item at position index
.
print('natural_numbers before removing last element:', natural_numbers)
natural_numbers.pop()
print('natural_numbers after removing last element:', natural_numbers)
natural_numbers before removing last element: [1, 10, 3, 4, 5, 20, 30, 40]
natural_numbers after removing last element: [1, 10, 3, 4, 5, 20, 30]
print('natural_numbers before removing element at position 4:', natural_numbers)
fourth_item = natural_numbers.pop(4)
print('natural_numbers after removing element at position 4:', natural_numbers)
print('we removed this item from the list:', fourth_item)
natural_numbers before removing element at position 4: [1, 10, 3, 4, 5, 20, 30]
natural_numbers after removing element at position 4: [1, 10, 3, 4, 20, 30]
we removed this item from the list: 5
5.1.1.4. List comprehensions#
Another way to iterate over a list is through list comprehensions. The syntax for list comprehensions is:
new_list = [do something on item x for x in iterable_name (if condition==True)]
The condition is not mandatory to be included. Below we will see examples that include a condition part and examples that do not include a condition part. Also, the iterable_name
can be any iterable (list, tuple, set, dictionary). x
is the variable used to iterate the list, which will represent one element of the iterable per iteration, will be modified by the do something on item x
part, and will be appended to the new_list
.
As you can see, list comprehensions provide a shorthand syntax for creating a list by modifying or filtering elements of the current list, in only one line of code.
For example, to iterate over the elements of the letters
list we would write:
[print(letter, end=' ') for letter in letters];
p y t h o n
To illustrate the usefulness of list comprehension, take for example the following for
loop:
list_of_numbers = []
for i in range(0, 5, 2):
list_of_numbers.append(i**2)
list_of_numbers
[0, 4, 16]
The above loop creates a list of integers from the squares of the given range. We can do the same in one line of code using list comprehension:
[x**2 for x in range(0, 5, 2)]
[0, 4, 16]
Now suppose that we want to split the elements of the letters
list into vowels and consonants. The vowels in the English language are: a, e, i, o, u, y
, so we will have them in the vowels
list. We will have all the vowels from the letters
list in one list and all the consonants on another. For this we can use a list comprehension.
vowels = ['a', 'e', 'i', 'o', 'u', 'y']
vowels_in_letters = [letter for letter in letters if letter in vowels]
consonants_in_letters = [letter for letter in letters if letter not in vowels]
print('Vowels in letters:', vowels_in_letters)
print('Consonants in letters:', consonants_in_letters)
Vowels in letters: ['y', 'o']
Consonants in letters: ['p', 't', 'h', 'n']
There is a possibility to include more than one condition. In this case the syntax of the list comprehension changes a bit because the condition would come before the for-loop. The syntax would be:
new_list = [a if condition=True else a=b for a in iterable_name]
For example, the example above of separating the vowels from consonants would be written as:
vowels_list = []
consonants_list = []
[vowels_list.append(x) if x in vowels else consonants_list.append(x) for x in letters]
print('Vowels in letters:', vowels_in_letters)
print('Consonants in letters:', consonants_in_letters)
Vowels in letters: ['y', 'o']
Consonants in letters: ['p', 't', 'h', 'n']
Thus, using two conditions (if
/else
) in the statement above is equivalent to the two list-comprehensions in the previous example. In other words, this statement single-handedly does the job. Let us explain what it does. We are iterating over the letters
list using the variable x
. For each x
, we check if it is in the vowels
list defined in the previous code cell. If this condition is True
, then we append the letter x
to the vowels_list
, otherwise we append x
to the consonants_list
. So the first statement vowels_list.append(x)
is executed only if the condition in the if
part is True
, otherwise the statement in the else
part is executed. Fig. 5.3 illustrates this case too.

Fig. 5.3 List comprehension with two conditions#
5.1.1.5. Sorting lists#
When programming, quite often we face the need to sort inputs (inside sequences) according to some criteria. In Python there is a built-in function sorted()
, that can receive a list as an argument and sort it. Also there is the built-in method list_name.sort()
that is called on a list object and by default sorts a list in ascending order.
Important
The built-in function sorted()
, returns a new list and the passed list remains unchanged. The built-in list_name.sort()
method does the sorting in-place. This means that the original list will be modified.
For example:
letters.sort()
print('letters in ascending order:', letters)
letters.sort(reverse=True)
print('letters in descending order:', letters)
letters in ascending order: ['h', 'n', 'o', 'p', 't', 'y']
letters in descending order: ['y', 't', 'p', 'o', 'n', 'h']
As you can see, the sort()
method modifies the original list, letters
.
Sorting using the sorted()
built-in function:
sorted_numbers = sorted(natural_numbers)
print('natural_numbers', natural_numbers)
print('sorted_numbers', sorted_numbers)
natural_numbers [1, 10, 3, 4, 20, 30]
sorted_numbers [1, 3, 4, 10, 20, 30]
As you can see, the original list passed as argument, natural_numbers
is unchanged.
Caution
If you try to use the built-in function sorted()
or the built-in method sort()
with a list that contains mixed data-types you will get a TypeError
because it is not possible to compare values of different data-types.
print(sorted(mixed_list))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/var/folders/7f/7nw_x13n5q965rss_qz6061m0000gq/T/ipykernel_46911/4181370301.py in <module>
----> 1 print(sorted(mixed_list))
TypeError: '<' not supported between instances of 'str' and 'int'
5.1.1.6. Searching lists#
The main purpose of searching is to find out whether a list contains the value we are searching for or not. The value that we are searching for is called a key. So in other words, we want to find out whether a list contains a key or not. In Python there is a built-in method that helps us do this:
list_name.index(key_name)
As we have seen many times so far, since index()
is a built-in method, we are calling it on a list object; in other words, we search for the key_name
inside the list_name
. This method will return the first index where the item is found in the list.
Let us look at an example.
fermented_food = ['milk', 'yoghurt', 'beer', 'cider', 'tempeh', 'sauerkraut', 'kefir']
print(fermented_food.index('beer'))
2
Caution
If the key that you are searching for in the list does not exist then you will get a ValueError
.
print(fermented_food.index('wine'))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_19188\831525127.py in <module>
----> 1 print(fermented_food.index('wine'))
ValueError: 'wine' is not in list
Hint
To escape this kind of error Python provides two operators: in and not in. in checks whether an element is part of a list and returns True
if so, otherwise False
. not in does the opposite, it checks whether an element is not in a list and if it is not, returns True
, otherwise it returns False
.
print('wine' in fermented_food)
print('wine' not in fermented_food)
False
True
There are also variants of the index()
method that allow us to specify the start and end indices, or only one of them in the list that we want to search for. The syntax looks like this:
list_name.index(value, start_index, end_index)
Note that the end_index is non-inclusive.
print(fermented_food.index('beer', 1))
print(fermented_food.index('cider', 2, 5))
2
3
Caution
index()
does not allow any keyword arguments so it is not possible to specify only the end_index without the start_index. Also the search goes only in the forward direction (i.e., ascending order) even if you specify negative indices.
Next on, we will see another collection data type: tuples.