Lists, Tuples#
In this episode, we will discuss some iterable objects in Python, such as lists, tuples.
Previously, we talked about the int
data type, where each object could only store one specific value of this type. However, there are times when it is crucial to combine objects into a collection for easier manipulation and work with them.
Let’s begin by exploring the list
data type.
list
data type#
Imagine wanting to measure your stress level every five minutes. In this case, you would have 288 observations for just one day. However, storing and operating on this data in Python would require 288 different variables, which is not very efficient.
To address this issue, we can use lists in Python. Here is an example of how you can create a list in Python:
stress_level = [0, 0, 10, 20, 100, 0, 0]
In general lists could contain different data types:
l = ["abc", 1, 42.0]
print(l)
Show code cell output
['abc', 1, 42.0]
Indexing#
Let’s create the following list:
numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
In reality, each element in a list is associated with an index. This allows us to access specific elements from our list.
In Python, indexing starts from 0
, so the first element in a list is associated with the index 0
.
Now let’s use indexing to access elements from a list.Here are some examples:
print("numbers[0]:", numbers[0])
print("numbers[3]:", numbers[3])
Show code cell output
numbers[0]: 10
numbers[3]: 40
We can change specific elements of our list by using the indexes:
numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
numbers[0] = -100
numbers[-1] = 0
print(numbers)
Show code cell output
[-100, 20, 30, 40, 50, 60, 70, 80, 90, 0]
Slicing#
There are times when we need to retrieve a specific part of the list, not just a single value.
To efficiently do that, we can use slicing techniques.
The concept is somewhat similar to the range
function that we previously learned in the course.
The general syntax for slicing is as follows:
list_object[start:stop:step]
Here:
start
is the starting point from which we want to begin our slice. Default value is0
.stop
the position where we want to stop the slicing (take a look at the fact that the value itself is not included in the range, just like with therange
function). Default value is length of the list object.step
is the step for our slicing. Default value is1
Let’s take a look on some exapmles:
numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
print("numbers[:3]:", numbers[:3]) # same as numbers[0:3:1]
print("numbers[3:]:", numbers[3:]) # same as numbers[3:10:1]
print("numbers[::2]:", numbers[::2]) # same as numbers[0:10:2]
Show code cell output
numbers[:3]: [10, 20, 30]
numbers[3:]: [40, 50, 60, 70, 80, 90, 100]
numbers[::2]: [10, 30, 50, 70, 90]
list
assignments#
Let’s examine the list assignment, as it may appear challenging at first glance.
numbers_1 = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
numbers_2 = numbers_1
numbers_2[0] = 100
What do you think stores in the numbers_1
variable?
numbers_1
corresponds to the following list: [100, 20, 30, 40, 50, 60, 70, 80, 90, 100]
This happens because in reality, in this specific case, the computer memory contains a dedicated place that contains the values of our list. After we specify a variable, it simply points to this object. When we use numbers_2 = numbers_1
, we do not create another object (meaning we don’t copy it), but rather create another pointer to the same object.
Because of this, when we modify numbers_2
, we are also modifying the underlying object, which is the same as the numbers_1
variable.
If you need to create an actual copy of the list, you can use slicing. Slicing always results in a new list. Here is an example:
numbers_1 = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
numbers_2 = numbers_1[::]
numbers_2[0] = 100
print("numbers_1:", numbers_1)
print("numbers_2:", numbers_2)
Show code cell output
numbers_1: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
numbers_2: [100, 20, 30, 40, 50, 60, 70, 80, 90, 100]
However, if you want to check whether the underlying objects of the variables are the same, you can use the built-in id
function. Here is how it works:
numbers_1 = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
numbers_2 = numbers_1
print("Ids after numbers_2 = numbers_1")
print(id(numbers_1), id(numbers_2))
numbers_2 = numbers_1[::]
print("Ids after numbers_2 = numbers_1[::]")
print(id(numbers_1), id(numbers_2))
Show code cell output
Ids after numbers_2 = numbers_1
139707870532480 139707870532480
Ids after numbers_2 = numbers_1[::]
139707870532480 139707870534208
As you can see in the first case, the results of the “id” calls are the same. However, when we use slicing, we have two different underlying objects in memory.
list
methods#
Before I did not mention it, but in reality, each object in Python has specific features that are specified by its data type. Lists are no exception. These features are called methods. Let’s take a look at some of them. You can append new element to list:
stress_level = [0, 0, 10, 20, 100, 0, 0]
print("before append:", stress_level)
stress_level.append(100)
print("after append:", stress_level)
Show code cell output
before append: [0, 0, 10, 20, 100, 0, 0]
after append: [0, 0, 10, 20, 100, 0, 0, 100]
As you can see here, we use the following syntax: we put a .
after the variable name and then specify the method name - append
.
There are many more different methods associated with the list
data type.
Let’s take a look on some other examples:
stress_level = [0, 0, 10, 20, 100, 0, 0]
stress_level.extend([90, 80, 70])
print("After extend:", stress_level)
stress_level.pop()
print("After pop:", stress_level)
stress_level.sort()
print("After sort:", stress_level)
Show code cell output
After extend: [0, 0, 10, 20, 100, 0, 0, 90, 80, 70]
After pop: [0, 0, 10, 20, 100, 0, 0, 90, 80]
After sort: [0, 0, 0, 0, 10, 20, 80, 90, 100]
If you want to remove an element, you can use the method remove
. However, if you prefer to remove the element by index, you can use the del
operator instead of the remove
method. Here is an example of how it works:
stress_level = [0, 0, 10, 20, 100, 0, 0]
print('stress_level before del:', stress_level)
del stress_level[2]
print('stress_level after del:', stress_level)
Show code cell output
stress_level before del: [0, 0, 10, 20, 100, 0, 0]
stress_level after del: [0, 0, 20, 100, 0, 0]
In the beginning, we will rely on the predefined methods that come with Python data objects by default. However, eventually, we will learn how to write our own data types (classes) and specify the methods we need.
Operations with list
-s#
You can use the following operators with lists: +
and *
.
+
- concatenates two lists into one, similar to how it works with strings.*
- repeats elements of a list a specified number of times. Examples:
# we can create list filled with zeros
stress_level = [0] * 10
print(stress_level)
stress_level = stress_level + [10] * 3
print(stress_level)
Show code cell output
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10]
len
function#
In python there is very easy and convinient way to get the length of the object.
To get the length you can simply use the len
function.
numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
print(len(numbers))
Show code cell output
10
for
loop and lists#
Now, let’s imagine that we want to iterate over each element in a list and perform some action on it. In order to do this, we can use a for
loop.
First, let’s start by creating the list using a for
loop:
numbers = [] # here we create an empty list
for i in range(10):
numbers.append(i)
print(numbers)
Show code cell output
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Now we can perform some actions over the list:
for i in range(len(numbers)):
print(numbers[i] ** 2)
Show code cell output
0
1
4
9
16
25
36
49
64
81
For example, we took each element from the list and calculated its square value. Take a look that we did not modify the original list, since we just print some values. However, if you want to modify values you can use indexing and reassign operator:
print("List before modification:", numbers)
for i in range(len(numbers)):
numbers[i] = numbers[i] * numbers[i]
print("List after modification:", numbers)
Show code cell output
List before modification: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
List after modification: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Note
Python offers an alternative method for iterating over list elements using the for
loop. Instead of using the range
function and index variable i
, we can directly specify a variable to hold a copy of each element during the iteration. Let’s take a look at that:
for elem in numbers:
print(elem, end = " ")
Show code cell output
0 1 4 9 16 25 36 49 64 81
In this example, instead of iterating over all possible indexes, we are directly iterating through the elements. We used the variable name elem
and replaced the range
function with the name of the list object.
A key moment here is that the variable elem
is associated with a new object that is a copy of the real element in the list. This means that we cannot directly change the elements of the list in-place.
Here is the example:
numbers = [0, 1, 2, 3, 4]
for elem in numbers:
elem = elem + 5
print(numbers)
Show code cell output
[0, 1, 2, 3, 4]
So, you can use this approach if you do not want to modify the values in your list.
List comprehension#
List comprehension is very cool way to define the list and fill it. We saw previously this way to fill list with using for loop:
numbers = []
for i in range(10):
numbers.append(i)
print(numbers)
Show code cell output
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
However, it requires a total of 4 lines of code to perform this simple action. In such situations, we can use list comprehension to simplify it.
numbers = [i for i in range(10)]
print(numbers)
Show code cell output
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Cool, isn’t it? It appears that we have placed the for loop inside the list declaration. Let’s split it into two parts:
i
: This is the value that will be placed inside the list. You can modify it and check the results.for i in range(10)
: This statement specifies the number of objects and the elements that should be used as a specimen to be placed into the list.
Also we can specify some condition which will filter out elements from the list which don’t meet some property:
numbers = [i * 10 for i in range(10) if (i * 10) >= 50]
print(numbers)
Show code cell output
[50, 60, 70, 80, 90]
Here we have three parts:
The element
i * 10
will be added to the list.The statement
for i in range(10)
specifies the range fori
.The condition
if i >= 50
is used as a filter to keep only those elements that meet the given criteria.
Additionally, you can use multiple for loops within a list comprehension.
pairs = [str(i) + ", " + str(j) for i in range(3) for j in range(4)]
print(pairs)
Show code cell output
['0, 0', '0, 1', '0, 2', '0, 3', '1, 0', '1, 1', '1, 2', '1, 3', '2, 0', '2, 1', '2, 2', '2, 3']
Warning
List comprehensions are like shortcuts in Python for creating lists. They’re great when you want to make a new list by applying a simple operation to every item in an existing list. For example, if you have a list of numbers and want to double each one, a list comprehension is perfect.
However, you should avoid list comprehensions when things get too complex. If you need to write a long, convoluted expression or if you’re not working with lists, but instead need to perform actions that involve multiple lines of code or complex logic, it’s better to use a regular for
loop. List comprehensions are like race cars on a straight track; they’re fast and efficient, but when the road gets twisty, it’s better to go with the steady and reliable option.
tuple
data type#
Tuples are another example of a data type that can store multiple objects. However, once you create a tuple
object, you cannot change its elements. To create a tuple
object, use parentheses ()
.
coordinates = (3, 4)
In this example, we’ve created a tuple called coordinates with two values, (3, 4).
You can access the elements of a tuple object using indexing, in the same way as we did with a list.
x = coordinates[0]
y = coordinates[1]
print(x, y)
Show code cell output
3 4
However, if you try to change an element of a tuple object, you will get an error. You can try executing the following code:
coordinates = (3, 4)
coordinates[0] = 0
The error message is the following:
TypeError: 'tuple' object does not support item assignment
Now, let’s highlight some key differences between tuples and lists:
Immutability: Tuples are immutable, meaning you can’t change their contents once created, while lists are mutable, so you can modify them.
Syntax: Tuples use parentheses
()
for creation, while lists use square brackets[]
.Use Cases: Tuples are useful when you want to ensure data remains constant, like latitude and longitude coordinates or information in a database record. Lists, being mutable, are better when you need a flexible collection to add or remove items.
So, in summary, when you want data that won’t change, use a tuple, and when you need flexibility, go with a list. It’s like choosing between a sealed bag (tuple) and a versatile backpack (list) based on your specific needs in Python.
Similarities Between Lists and Tuples#
Ordered Collections#
Both lists and tuples maintain the order of elements. This means that the items in a list or tuple are indexed, and you can access elements by their position in the sequence.
Indexing and Slicing#
You can access individual elements using zero-based indexing, and you can slice them to get sub-sequences. For example:
my_list = [1, 2, 3, 4]
my_tuple = (1, 2, 3, 4)
# Accessing elements
print(my_list[2]) # Output: 3
print(my_tuple[2]) # Output: 3
# Slicing
print(my_list[1:3]) # Output: [2, 3]
print(my_tuple[1:3]) # Output: (2, 3)
3
3
[2, 3]
(2, 3)
Iteration#
Both lists and tuples support iteration. You can use loops (e.g., for
loops) to iterate over the elements in a list or a tuple:
for item in my_list:
print(item, end=" ")
print()
for item in my_tuple:
print(item, end=" ")
1 2 3 4
1 2 3 4
Support for Membership Testing#
Both lists and tuples in Python support membership testing using the in
and not in
operators, which allow you to check whether an element exists in the collection or not.
You can check if an item exists in a list or a tuple using the in
keyword:
my_list = [1, 2, 3, 4]
my_tuple = (1, 2, 3, 4)
print(3 in my_list)
print(10 in my_list)
print(3 in my_tuple)
print(0 in my_tuple)
True
False
True
False
The not in
operator checks if an element is not present in a list. It returns True
if the element is not found and False
otherwise.
my_list = [1, 2, 3, 4]
# Check if 5 is not in the list
print(5 not in my_list) # Output: True
# Check if 2 is not in the list
print(2 not in my_list) # Output: False
True
False
Similarly, the not in
operator checks if an element is not present in a tuple. It behaves in the same way as with lists, returning True
if the element is not found and False
otherwise.
Length#
Both lists and tuples support the len()
function, which returns the number of elements in the collection:
print(len(my_list)) # Output: 4
print(len(my_tuple)) # Output: 4
4
4
Summary#
While lists and tuples have several similarities, such as being ordered, indexable, iterable, and capable of containing multiple data types and nested structures, they differ primarily in their mutability:
Lists are mutable, meaning their elements can be modified after creation. You can add, remove, or change elements in a list.
Tuples are immutable, meaning once created, their elements cannot be changed. This immutability makes tuples useful for fixed data that shouldn’t change.