Takeaways from Python Crash Course: Python List and Tuple

Aug. 13, 2024

This post is a record made while learning Chapter 3 “Introducing Lists” and Chapter 4 “ Working with Lists” in Eric Matthes’s book, Python Crash Course.1

List

Create a list

1
2
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles)
1
['trek', 'cannondale', 'redline', 'specialized']

Index a list element

The starting index (position) of Python list is 0, rather than 1, that is Python list is zero-based numbering, so we should note off-by-one error.

Off-by-one error2: An off-by-one error or off-by-one bug (known by acronyms OBOE, OBO, OB1 and OBOB) is a logic error that involves a number that differs from its intended value by +1 or −1. An off-by-one error can sometimes appear in a mathematical context. It often occurs in computer programming when a loop iterates one time too many or too few, usually caused by the use of non-strict inequality (≤) as the terminating condition where strict inequality (<) should have been used, or vice versa. Off-by-one errors also stem from confusion over zero-based numbering.

1
2
3
4
5
6
7
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles[0])
print(bicycles[0].title()) # Format the element, 'Trek' is capitalized.
print(bicycles[1])
print(bicycles[3])
print(bicycles[-1]) # Access the last element in a list
print(bicycles[-2])
1
2
3
4
5
6
trek
Trek
cannondale
specialized
specialized
redline

Use individual values from a List

1
2
3
# use f-strings to create a message based on a value from a list
message = f"My first bicycle was a {bicycles[0].title()}."
print(message)
1
My first bicycle was a Trek.

Modify elements in a list

1
2
3
4
5
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)

motorcycles[0] = 'ducati'
print(motorcycles)
1
2
['honda', 'yamaha', 'suzuki']
['ducati', 'yamaha', 'suzuki']

Append elements to a list (append method)

Python append method makes new element added to the end of the list.

1
2
3
4
5
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)

motorcycles.append('ducati')
print(motorcycles)
1
2
['honda', 'yamaha', 'suzuki']
['honda', 'yamaha', 'suzuki', 'ducati']

It’s common to build a list from empty by appending item, which is unknown until after program is running, one by one during program execution.

1
2
3
4
5
6
7
motorcycles = [] # Define an empty list to store values

motorcycles.append('honda')
motorcycles.append('yamaha')
motorcycles.append('suzuki')

print(motorcycles)
1
['honda', 'yamaha', 'suzuki']

Extend elements to a list (extend method)

1
2
3
4
5
6
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)

motorcycles1 = ['yamaha', 'suzuki']
motorcycles.extend(motorcycles1)
print(motorcycles)
1
2
['honda', 'yamaha', 'suzuki']
['honda', 'yamaha', 'suzuki', 'yamaha', 'suzuki']

Concatenate several lists

1
2
3
4
5
6
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)

motorcycles1 = ['yamaha', 'suzuki']
motorcycles2 = ['honda', 'yamaha']
print(motorcycles + motorcycles1 + motorcycles2)
1
2
['honda', 'yamaha', 'suzuki']
['honda', 'yamaha', 'suzuki', 'yamaha', 'suzuki', 'honda', 'yamaha']

Insert elements into a list (insert method)

Insert elements into a list by insert method.

1
2
3
4
5
6
7
motorcycles = ['honda', 'yamaha', 'suzuki']

motorcycles.insert(0, 'ducati')
print(motorcycles)

motorcycles.insert(2, 'ducati2')
print(motorcycles)
1
2
['ducati', 'honda', 'yamaha', 'suzuki']
['ducati', 'honda', 'ducati2', 'yamaha', 'suzuki']

The insert method opens a space at position 0 and stores the value 'ducati' at that location. This operation shifts every other value in the list one position to the right.

Remove elements from a list

del statement

1
2
3
4
5
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)

del motorcycles[0]
print(motorcycles)
1
2
['honda', 'yamaha', 'suzuki']
['yamaha', 'suzuki']
1
2
3
4
5
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)

del motorcycles[1]
print(motorcycles)
1
2
['honda', 'yamaha', 'suzuki']
['honda', 'suzuki']

pop method

The pop method removes the last item in a list, but the item is still available after removing.

1
2
3
4
5
6
7
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)

popped_motorcycles = motorcycles.pop()
# pop a value from the list and store that value in the variable `popped_motorcycles`
print(motorcycles) 
print(popped_motorcycles)
1
2
3
['honda', 'yamaha', 'suzuki']
['honda', 'yamaha']
suzuki

Pop item from any position in a list:

1
2
3
4
5
motorcycles = ['honda', 'yamaha', 'suzuki']

first_owned = motorcycles.pop(0)
print(motorcycles)
print(f"The first motorcycle I owned was a {first_owned.title()}.")
1
2
['yamaha', 'suzuki']
The first motorcycle I owned was a Honda.

remove method

1
2
3
4
5
motorcycles = ['honda', 'yamaha', 'suzuki', 'ducati']
print(motorcycles)

motorcycles.remove('ducati')
print(motorcycles)
1
2
['honda', 'yamaha', 'suzuki', 'ducati']
['honda', 'yamaha', 'suzuki']
1
2
3
4
5
6
7
motorcycles = ['honda', 'yamaha', 'suzuki', 'ducati']
print(motorcycles)

too_expensive = 'ducati'
motorcycles.remove(too_expensive)
print(motorcycles)
print(f"\nA {too_expensive.title()} is too expensive for me.")
1
2
3
4
['honda', 'yamaha', 'suzuki', 'ducati']
['honda', 'yamaha', 'suzuki']

A Ducati is too expensive for me.

The value 'ducati' has been removed from the list but is still accessible through the variable too_expensive.

The remove method deletes only the first occurrence of the specified value. If the value appears more than once in the list, we should use a loop to make sure all occurrences of the value are removed.

1
2
3
4
5
6
7
motorcycles = ['honda', 'yamaha', 'honda', 'suzuki', 'honda', 'ducati', 'honda']

print(motorcycles)
while 'honda' in motorcycles:
    motorcycles.remove('honda')
 
print(motorcycles)
1
2
['honda', 'yamaha', 'honda', 'suzuki', 'honda', 'ducati', 'honda']
['yamaha', 'suzuki', 'ducati']

Organize a list

sort method

Sort a list permanently with the sort method.

1
2
3
4
cars = ['bmw', 'audi', 'toyota', 'subaru']
cars.sort()
print(cars)
# The cars are now in alphabetical order, and we can never revert to the original order.
1
['audi', 'bmw', 'subaru', 'toyota']

and in reverse alphabetical order:

1
2
3
cars = ['bmw', 'audi', 'toyota', 'subaru']
cars.sort(reverse=True)
print(cars)
1
['toyota', 'subaru', 'bmw', 'audi']

sorted function

Sort a list temporarily with the sorted function: maintain the original order of a list but present it in a sorted order.

In alphabetical order:

1
2
3
4
5
cars = ['bmw', 'audi', 'toyota', 'subaru']

print(cars)
print(sorted(cars))
print(cars)
1
2
3
['bmw', 'audi', 'toyota', 'subaru']
['audi', 'bmw', 'subaru', 'toyota']
['bmw', 'audi', 'toyota', 'subaru']

In reverse alphabetical order:

1
2
3
4
5
cars = ['bmw', 'audi', 'toyota', 'subaru']

print(cars)
print(sorted(cars,reverse=True))
print(cars)
1
2
3
['bmw', 'audi', 'toyota', 'subaru']
['toyota', 'subaru', 'bmw', 'audi']
['bmw', 'audi', 'toyota', 'subaru']

It is more complicated to sort a list alphabetically when all the values are not in lowercase. There are several ways to interpret capital letters when determining a sort order.

reverse method

To reverse the original order of a list, we can use the reverse method: reverse doesn’t sort backward alphabetically; it simply reverses the order of the list.

reverse method changes the order of a list permanently, but we can revert to the original order anytime by applying reverse to the same list a second time.

1
2
3
4
5
6
cars = ['bmw', 'audi', 'toyota', 'subaru']
print(cars)
cars.reverse()
print(cars)
cars.reverse()
print(cars)
1
2
3
['bmw', 'audi', 'toyota', 'subaru']
['subaru', 'toyota', 'audi', 'bmw']
['bmw', 'audi', 'toyota', 'subaru']

len function

1
2
cars = ['bmw', 'audi', 'toyota', 'subaru']
len(cars)
1
4

Python counts the items in a list starting with one, so off-by-one error doesn’t exist when determining the length of a list.

Loop through an entire list by for loop

1
2
3
4
5
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
    print(magician)
    print(f"{magician.title()}, that was a great trick!")
    print(f"I can't wait to see your next trick, {magician.title()}.\n")
1
2
3
4
5
6
7
8
9
10
11
alice
Alice, that was a great trick!
I can't wait to see your next trick, Alice.

david
David, that was a great trick!
I can't wait to see your next trick, David.

carolina
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.

Numerical lists

range function

1
2
for value in range(1,5):
    print(value)
1
2
3
4
1
2
3
4

More of the same, note the off-by-one behavior in this case: the range function causes Python to start counting at the first value we give it, and it stops when it reaches the second value provided. Because it stops at that second value, the output never contains the end value, which would have been 5 in this case.

We can also pass range only one argument, and it will start the sequence of numbers at 0.

1
2
for value in range(6):
    print(value)
1
2
3
4
5
6
0
1
2
3
4
5

Create a numerical list by range + list

1
2
numbers = list(range(1, 6))
print(numbers)
1
[1, 2, 3, 4, 5]

Skip numbers in a given range by inputing the third argument (i.e. step size):

1
2
even_numbers = list(range(2, 11, 2))
print(even_numbers)
1
[2, 4, 6, 8, 10]

In this example, the range function starts with the value 2 and then adds 2 to that value. It adds 2 repeatedly until it reaches or passes the end value 11.

Application of range function in for loop

1
2
3
4
5
squares = []
for value in range(1, 11):
    squares.append(value ** 2)
    
print(squares)
1
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Simple statistics of a numerical list

1
2
digits = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
print(min(digits), max(digits), sum(digits))
1
0 9 45

List comprehensions

A list comprehension allows you to generate this same list in just one line of code: a list comprehension combines the for loop and the creation of new elements into one line, and automatically appends each new element.

1
2
squares = [value**2 for value in range(1, 11)]
print(squares)
1
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Slice a list

Case 1

1
2
3
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players)
print(players[0:3])
1
2
['charles', 'martina', 'michael', 'florence', 'eli']
['charles', 'martina', 'michael']

Case 2: with no the first index

1
2
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[:4])
1
['charles', 'martina', 'michael', 'florence']

Case 3: with no the last index

1
2
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[2:])
1
['michael', 'florence', 'eli']

Case 4: output the last $n$ items

1
2
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[-3:])
1
['michael', 'florence', 'eli']

Case 5: include a third value in the brackets indicating a step size

If a third value is included, this tells Python how many items to skip between items in the specified range.

1
2
3
players = ['charles', 'martina', 'michael', 'florence', 'eli', 'tommy', 'Merry']

print(players[1::2])
1
['martina', 'florence', 'tommy']

Case 6: Loop through a slice

1
2
3
4
5
players = ['charles', 'martina', 'michael', 'florence', 'eli']

print("Here are the first three players on my team:")
for player in players[:3]:
    print(player.title())
1
2
3
4
Here are the first three players on my team:
Charles
Martina
Michael

More ways to slice list can be found in GeeksforGeeks blog3.

Copy a list: [:], copy method, and equal sign

To copy a list, we can make a slice that includes the entire original list by omitting the first index and the second index, i.e. [:], and at this point we have two separate lists:

1
2
3
4
5
6
7
8
9
10
11
my_foods = ['pizza', 'falafel', 'carrot cake']
friend_foods = my_foods[:]

print(my_foods)
print(friend_foods)

my_foods.append('cannoli')
friend_foods.append('ice cream')

print(my_foods)
print(friend_foods)
1
2
3
4
['pizza', 'falafel', 'carrot cake']
['pizza', 'falafel', 'carrot cake']
['pizza', 'falafel', 'carrot cake', 'cannoli']
['pizza', 'falafel', 'carrot cake', 'ice cream']

And, copy method can reach the same effect:

1
2
3
4
5
6
7
8
9
10
11
my_foods = ['pizza', 'falafel', 'carrot cake']
friend_foods = my_foods.copy()

print(my_foods)
print(friend_foods)

my_foods.append('cannoli')
friend_foods.append('ice cream')

print(my_foods)
print(friend_foods)
1
2
3
4
['pizza', 'falafel', 'carrot cake']
['pizza', 'falafel', 'carrot cake']
['pizza', 'falafel', 'carrot cake', 'cannoli']
['pizza', 'falafel', 'carrot cake', 'ice cream']

If we had simply set friend_foods equal to my_foods by an equal sign, we would NOT produce two separate lists.

1
2
3
4
5
6
7
8
9
my_foods = ['pizza', 'falafel', 'carrot cake']

friend_foods = my_foods

my_foods.append('cannoli')
friend_foods.append('ice cream')

print(my_foods)
print(friend_foods)
1
2
['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']
['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']

This syntax (friend_foods = my_foods) actually tells Python to associate the new variable friend_foods. At this time, my_foods is a reference to friend_foods4, both variables pointing to the same list, so changes made in my_foods will automatically also be made in friend_foods, and vice versa.


Tuple

Create a tuple and index whose elements

A tuple looks just like a list except we use parentheses instead of square brackets.

1
2
dimensions = (200, 50)
print(dimensions[0], dimensions[1])
1
200 50

Tuples are technically defined by the presence of a comma; the parentheses make them look neater and more readable. If we want to define a tuple with one element, we need to include a trailing comma:

1
2
my_t = (3,)
print(my_t)
1
(3,)

It doesn’t often make sense to build a tuple with one element, but this can happen when tuples are generated automatically.

Slice a tuple

The way to slice a tuple is the same as we do for list.

1
2
3
my_t = (1, 3, 8, 7)
print(my_t)
print(my_t[:2])
1
2
(1, 3, 8, 7)
(1, 3)

Create a numerical tuple by range + tuple

1
2
numbers = tuple(range(1, 6))
print(numbers)
1
(1, 2, 3, 4, 5)

Concatenate several tuples

1
2
3
4
my_t1 = (1, 3, 8, 7)
my_t2 = (1, 3, 8, 7)
my_t3 = (1, 3, 8, 7)
print(my_t1 + my_t2 + my_t3)
1
(1, 3, 8, 7, 1, 3, 8, 7, 1, 3, 8, 7)

Loop through all values in a tuple

1
2
3
dimensions = (200, 50)
for dimension in dimensions:
    print(dimension)
1
2
200
50

Tuple is an immutable object

Tuple is an immutable object, so it is useful when we want to create a list of items that cannot change.

1
2
dimensions = (200, 50)
dimensions[0] = 250
1
2
3
4
5
6
7
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[43], line 2
      1 dimensions = (200, 50)
----> 2 dimensions[0] = 250

TypeError: 'tuple' object does not support item assignment

Although we can’t modify a tuple, we can reassign a new value to an existing variable to write over a tuple.

1
2
3
4
5
6
7
dimensions = (200, 50)
for dimension in dimensions:
    print(dimension)
 
dimensions = (400, 100)
for dimension in dimensions:
    print(dimension)
1
2
3
4
200
50
400
100

Compared with list, Python tuple is a kind of simpler data structure, we can use them when we need specify a set of values that should not be changed through-out the life of a program.


Python mutable vs. immutable objects

As described before, Python tuple is a kind of immutable object, and on the contrary, list is a kind of mutable object. Blog “Mutable vs Immutable Objects in Python”5 in GeeksforGeeks compares Python mutable and immutable objects in detail:

In Python, Every variable in Python holds an instance of an object. There are two types of objects in Python i.e. Mutable and Immutable objects. Whenever an object is instantiated, it is assigned a unique object id. The type of the object is defined at the runtime and it can’t be changed afterward. However, its state can be changed if it is a mutable object.

Immutable Objects are of in-built datatypes like int, float, bool, string, Unicode, and tuple. In simple words, an immutable object can’t be changed after it is created.

Mutable Objects are of type Python list, Python dict, or Python set. Custom classes are generally mutable.

Python’s Mutable vs Immutable

  1. Mutable and immutable objects are handled differently in Python. Immutable objects are quicker to access and are expensive to change because it involves the creation of a copy. Whereas mutable objects are easy to change.
  2. The use of mutable objects is recommended when there is a need to change the size or content of the object.
  3. The tuple itself isn’t mutable but contains items that are mutable. As a rule of thumb, generally, Primitive-like types are probably immutable, and Customized Container-like types are mostly mutable.

Here are some verifications for above conclusions.

Python string and int are immutable

1
2
3
greeting = "Hello, world!"
greeting[0] = 'G'
print(greeting)
1
2
3
4
5
6
7
8
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 2
      1 greeting = "Hello, world!"
----> 2 greeting[0] = 'G'
      3 print(greeting)

TypeError: 'str' object does not support item assignment
1
2
a = 2
a[0] = 3
1
2
3
4
5
6
7
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[4], line 2
      1 a = 2
----> 2 a[0] = 3

TypeError: 'int' object does not support item assignment

Python dict and set are mutable

1
2
3
4
5
6
7
my_dict = {"name": "Ram", "age": 25}
print(my_dict)

new_dict = my_dict
new_dict["age"] = 37
print(my_dict)
print(new_dict)
1
2
3
{'name': 'Ram', 'age': 25}
{'name': 'Ram', 'age': 37}
{'name': 'Ram', 'age': 37}
1
2
3
4
5
6
7
my_set = {1, 2, 3}
print(my_set) 

new_set = my_set
new_set.add(4)
print(my_set)
print(new_set)
1
2
3
{1, 2, 3}
{1, 2, 3, 4}
{1, 2, 3, 4}

Features showed here is the same as that of the list.

The tuple per se isn’t mutable but whose items are mutable

1
2
3
4
5
6
7
tup = ([3, 4, 5], 'string1')
print(tup)

tup[0][1] = 101
print(tup)

tup[1] = 'string2'
1
2
3
4
5
6
7
8
9
10
([3, 4, 5], 'string1')
([3, 101, 5], 'string1')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[1], line 7
      4 tup[0][1] = 101
      5 print(tup)
----> 7 tup[1] = 'string2'

TypeError: 'tuple' object does not support item assignment

Space and time complexity of list and tuple

Tuple is more memory-efficient than list; the implication of iterations of list is more faster that of tuple:6

As tuples are stored in a single memory block therefore they don’t require extra space for new objects whereas the lists are allocated in two blocks, first the fixed one with all the Python object information and second a variable-sized block for the data which makes them even faster.

1
2
3
4
5
6
7
import sys
a_list = []
a_tuple = ()
a_list = ["Geeks", "For", "Geeks"]
a_tuple = ("Geeks", "For", "Geeks")
print(sys.getsizeof(a_list))
print(sys.getsizeof(a_tuple))
1
2
88
64
1
2
3
4
5
6
7
8
9
import timeit

print("Elapsed time is:",timeit.timeit(
    stmt='a = [a for a in list(range(500000))]',
    number=50000))

print("Elapsed time is:",timeit.timeit(
    stmt='a = [a for a in tuple(range(500000))]',
    number=50000))
1
2
Elapsed time is: 793.0124018999995
Elapsed time is: 822.4327848000003

However, in the same blog6, there are conflicting statements about the pros and cons of list and tuple performance:

List: The implication of iterations is Time-consuming

Tuple: The implication of iterations is comparatively Faster

When to Use Tuples Over Lists?

Immutable Data: Tuples are immutable, thus once they are generated, their contents cannot be changed. This makes tuples a suitable option for storing information that shouldn’t change, such as setup settings, constant values, or other information that should stay the same while your programme is running.

Performance: Tuples are more lightweight than lists and might be quicker to generate, access, and iterate through since they are immutable. Using a tuple can be more effective than using a list if you have a huge collection of data that you need to store, retrieve, and use regularly and that data does not need to be altered.

Data integrity: By ensuring that the data’s structure and contents stay consistent, tuples can be utilised to ensure data integrity. To make sure the caller is aware of how much data to expect, for instance, if a function returns a set amount of values, you might want to return them as a tuple rather than a list.

I’m not certain which is right, and at this moment I won’t step further about this point.


References