Programming with Python | Chapter 5: Data Structures – Lists and Tuples
Chapter Objectives
- Understand the concept of data structures for organizing collections of items.
- Learn how to create and manipulate lists: ordered, mutable sequences.
- Access list elements using indexing and slicing, similar to strings.
- Modify lists using methods like
.append()
,.insert()
,.remove()
,.pop()
, and index assignment. - Sort and reverse lists using
.sort()
and.reverse()
. - Learn how to create and use tuples: ordered, immutable sequences.
- Understand the key differences between lists and tuples, particularly mutability.
- Recognize appropriate use cases for lists and tuples.
- Use the
len()
function with lists and tuples.
Introduction
While variables hold single pieces of data, we often need to work with collections of related items. Data structures provide ways to organize and store multiple values. This chapter introduces two fundamental sequence-based data structures in Python: lists and tuples. Both allow you to store an ordered collection of items, but they differ primarily in their mutability: lists can be changed after creation, while tuples cannot. We will explore how to create, access, modify (for lists), and use these versatile structures.
Theory & Explanation
What are Data Structures?
Data structures are specialized formats for organizing, processing, retrieving, and storing data. They allow us to manage collections of data efficiently. Python offers several built-in data structures, and lists and tuples are often the first ones programmers learn because they represent ordered sequences.
Lists: Ordered, Mutable Collections
A list is an ordered sequence of items, enclosed in square brackets []
, with items separated by commas. Lists are highly flexible because:
- Ordered: Items maintain their position (index).
- Mutable: You can change the list after it’s created (add, remove, or modify items).
- Heterogeneous: Items in a list can be of different data types (though often they are homogeneous for clarity).
Creating Lists:
empty_list = []
numbers = [1, 2, 3, 5, 8]
mixed_list = [1, "hello", 3.14, True, None]
list_of_lists = [[1, 2], [3, 4], [5, 6]] # Nested list
print(empty_list)
print(numbers)
print(mixed_list)
Accessing Elements (Indexing and Slicing):
Lists use the same zero-based indexing and slicing syntax as strings (Chapter 3).
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
# Indices: 0 1 2 3 4
# Neg Indices:-5 -4 -3 -2 -1
print(f"First fruit: {fruits[0]}") # apple
print(f"Last fruit: {fruits[-1]}") # elderberry
print(f"Middle fruits: {fruits[1:4]}") # ['banana', 'cherry', 'date'] (index 4 is exclusive)
print(f"First three: {fruits[:3]}") # ['apple', 'banana', 'cherry']
print(f"From index 2 onwards: {fruits[2:]}") # ['cherry', 'date', 'elderberry']
print(f"Every other fruit: {fruits[::2]}") # ['apple', 'cherry', 'elderberry']
Modifying Lists (Mutability):
Because lists are mutable, you can change them directly.
Changing an Item:
Use index assignment.
fruits[1] = "blueberry" # Replace 'banana'
print(fruits) # ['apple', 'blueberry', 'cherry', 'date', 'elderberry']
Adding Items:
.append(item)
: Addsitem
to the end of the list..insert(index, item)
: Insertsitem
at the specifiedindex
, shifting subsequent items..extend(iterable)
: Appends all items from another iterable (like another list) to the end.
fruits.append("fig")
print(fruits) # ['apple', 'blueberry', 'cherry', 'date', 'elderberry', 'fig']
fruits.insert(2, "mango") # Insert 'mango' at index 2
print(fruits) # ['apple', 'blueberry', 'mango', 'cherry', 'date', 'elderberry', 'fig']
more_fruits = ["grape", "honeydew"]
fruits.extend(more_fruits)
print(fruits) # ['apple', ..., 'fig', 'grape', 'honeydew']
Removing Items:
.remove(value)
: Removes the first occurrence of the specifiedvalue
. RaisesValueError
if the value is not found..pop(index=-1)
: Removes and returns the item at the specifiedindex
. If no index is given, it removes and returns the last item. RaisesIndexError
if the index is invalid or the list is empty.- .
del list_name[index]
: Deletes the item at the specifiedindex
. Can also delete slices (del list_name[start:stop]
).
fruits.remove("date")
print(fruits) # ['apple', 'blueberry', 'mango', 'cherry', 'elderberry', 'fig', 'grape', 'honeydew']
last_fruit = fruits.pop() # Remove and get 'honeydew'
print(f"Removed fruit: {last_fruit}")
print(fruits) # ['apple', ..., 'grape']
second_fruit = fruits.pop(1) # Remove and get 'blueberry' at index 1
print(f"Removed second fruit: {second_fruit}")
print(fruits) # ['apple', 'mango', 'cherry', 'elderberry', 'fig', 'grape']
del fruits[0] # Delete 'apple' at index 0
print(fruits) # ['mango', 'cherry', 'elderberry', 'fig', 'grape']
Indexing and Slicing
Indices in Python Sequences
Other Common List Methods:
Common List Methods
Method | Description | Example | Result |
---|---|---|---|
len(list) |
Returns the number of items | len([3, 1, 4, 1, 5]) |
5 |
list.append(item) |
Adds item to the end | a=[1,2]; a.append(3) |
a is [1, 2, 3] |
list.insert(idx, item) |
Inserts item at index idx |
a=[1,3]; a.insert(1, 2) |
a is [1, 2, 3] |
list.extend(iterable) |
Adds all items from iterable | a=[1,2]; a.extend([3,4]) |
a is [1, 2, 3, 4] |
list.remove(value) |
Removes first occurrence of value |
a=[1,2,2]; a.remove(2) |
a is [1, 2] |
list.pop([idx]) |
Removes & returns item at idx (default: last) |
a=[1,2,3]; x=a.pop(1) |
x is 2 , a is [1, 3] |
list.index(value) |
Returns index of first occurrence of value |
[1, 2, 2].index(2) |
1 |
list.count(value) |
Counts occurrences of value |
[1, 2, 2].count(2) |
2 |
list.sort() |
Sorts list in-place | a=[3,1,2]; a.sort() |
a is [1, 2, 3] |
list.reverse() |
Reverses list in-place | a=[1,2,3]; a.reverse() |
a is [3, 2, 1] |
list.copy() |
Creates a shallow copy | a=[1,2]; b=a.copy() |
b is [1, 2] (new list) |
list.clear() |
Removes all items from list | a=[1,2,3]; a.clear() |
a is [] |
Note: Methods like .sort()
, .reverse()
, .clear()
modify the list in-place and return None
.
Note on sort() and reverse():
These methods modify the list in-place and return None
. If you want a sorted copy without changing the original, use the sorted()
function: sorted_nums = sorted(nums)
.
Tuples: Ordered, Immutable Collections
A tuple is an ordered sequence of items, enclosed in parentheses ()
, with items separated by commas. Tuples are like lists, but with one crucial difference:
- Ordered: Items maintain their position (index).
- Immutable: Once a tuple is created, you cannot change its contents (no adding, removing, or modifying items).
- Heterogeneous: Items can be of different data types.
Creating Tuples:
empty_tuple = ()
point = (10, 20) # Represents coordinates, for example
mixed_tuple = (1, "hello", 3.14, True)
single_item_tuple = (42,) # NOTE the trailing comma! Without it, (42) is just the integer 42.
print(point)
print(mixed_tuple)
print(single_item_tuple)
print(type((42))) # <class 'int'>
print(type((42,))) # <class 'tuple'>
Visual Examples
Accessing Elements (Indexing and Slicing):
Tuples use the same indexing and slicing syntax as lists and strings.
colors = ("red", "green", "blue")
# Indices: 0 1 2
# Neg Indices:-3 -2 -1
print(colors[0]) # red
print(colors[-1]) # blue
print(colors[0:2]) # ('red', 'green')
Immutability in Action:
Attempting to modify a tuple after creation results in a TypeError
.
# colors[0] = "yellow" # This will raise a TypeError: 'tuple' object does not support item assignment
# colors.append("purple") # This will raise an AttributeError: 'tuple' object has no attribute 'append'
Why Use Tuples?
If tuples are immutable, why use them instead of lists?
- Data Integrity: Immutability guarantees that the tuple’s contents won’t accidentally change, which is useful for representing fixed collections like coordinates, RGB color values, or records.
- Performance: Tuples can be slightly more memory-efficient and faster to iterate over than lists (though this difference is often minor in practice).
- Dictionary Keys: Because they are immutable, tuples containing only immutable elements (like strings, numbers, other tuples) can be used as keys in dictionaries (Chapter 6), whereas lists cannot.
- Function Arguments/Return Values: Often used to pass or return multiple fixed values from functions.
Tuple Methods:
Tuples have far fewer methods than lists due to their immutability. The main ones are:
Method | Description | Example | Result |
---|---|---|---|
len(tuple) |
Returns the number of items | len((1, 2, 3)) |
3 |
tuple.count(value) |
Counts occurrences of value | (1, 2, 2, 3).count(2) |
2 |
tuple.index(value) |
Returns index of first occurrence | (1, 2, 3).index(2) |
1 |
Tuples have fewer methods than lists because they are immutable.
Tuple Unpacking
A convenient feature is tuple unpacking, which allows you to assign items from a tuple (or list) to individual variables.
coordinates = (100, 200, 50) # x, y, z
x, y, z = coordinates # Unpacking
print(f"x: {x}, y: {y}, z: {z}") # x: 100, y: 200, z: 50
# This also works with lists
dimensions = [30, 40]
width, height = dimensions
print(f"Width: {width}, Height: {height}") # Width: 30, Height: 40
The number of variables on the left must match the number of items in the sequence.
Lists vs Tuples Comparison
Feature | Lists [ ] |
Tuples ( ) |
---|---|---|
Mutability | Mutable (can be changed) | Immutable (cannot be changed) |
Syntax | [1, 2, 3] |
(1, 2, 3) |
Single item | [42] |
(42,) Needs trailing comma! |
Add elements | Yes .append() , .insert() , .extend() |
No Cannot add elements |
Remove elements | Yes .pop() , .remove() , del |
No Cannot remove elements |
Sorting | Yes .sort() |
No Cannot sort in-place |
Indexing | Yes my_list[0] |
Yes my_tuple[0] |
Slicing | Yes my_list[1:3] |
Yes my_tuple[1:3] |
Use as dictionary key | No | Yes (if containing only immutable elements) |
Performance | Slightly slower | Slightly faster |
Common uses | Collection that needs to be modified | Fixed data, function returns, coordinates |
Code Examples
Example 1: List Manipulation
# shopping_list.py
# Start with an initial list
shopping_items = ["milk", "bread", "eggs"]
print(f"Initial list: {shopping_items}")
# Add items
shopping_items.append("apples")
shopping_items.insert(1, "butter") # Insert at index 1
print(f"After adding: {shopping_items}")
# Check if an item exists
item_to_find = "bread"
if item_to_find in shopping_items: # The 'in' operator checks for membership
print(f"'{item_to_find}' is in the list.")
# Remove the item
shopping_items.remove(item_to_find)
print(f"After removing '{item_to_find}': {shopping_items}")
else:
print(f"'{item_to_find}' not found.")
# Get and remove the last item
last_item = shopping_items.pop()
print(f"Bought '{last_item}'. Remaining: {shopping_items}")
# Sort the list alphabetically
shopping_items.sort()
print(f"Sorted list: {shopping_items}")
# Get the length
print(f"Number of items left: {len(shopping_items)}")
Explanation:
- Demonstrates creating a list and modifying it using
.append()
,.insert()
,.remove()
, and.pop()
. - Uses the
in
keyword (a membership operator) to check if an item exists before trying to remove it. - Shows how
.pop()
removes and returns an item. - Uses
.sort()
to sort the list alphabetically in place. - Uses
len()
to find the number of items.
Example 2: Tuples for Fixed Data and Unpacking
# coordinates.py
# Representing fixed points using tuples
point1 = (10, 20)
point2 = (50, -15)
# Function that returns multiple values as a tuple
def get_screen_dimensions():
width = 1920
height = 1080
return (width, height) # Return a tuple
# Get dimensions and unpack
screen_w, screen_h = get_screen_dimensions()
print(f"Screen dimensions: Width={screen_w}, Height={screen_h}")
# Calculate distance (simplified example, not real distance formula)
x_diff = point2[0] - point1[0]
y_diff = point2[1] - point1[1]
print(f"Difference in coordinates: dx={x_diff}, dy={y_diff}")
# Trying to modify a tuple (will cause error if uncommented)
# point1[0] = 15 # TypeError
# Using tuples as dictionary keys (preview for Chapter 6)
locations = {
(40.7128, -74.0060): "New York City",
(34.0522, -118.2437): "Los Angeles"
}
print(f"Location at (40.7128, -74.0060): {locations[(40.7128, -74.0060)]}")
Explanation:
- Uses tuples
point1
andpoint2
to store fixed coordinate pairs. - A function
get_screen_dimensions
returns multiple values packaged in a tuple. - Tuple unpacking (
screen_w, screen_h = ...
) assigns the returned values directly to variables. - Accesses tuple elements using indexing (
point1[0]
). - Demonstrates (by commenting out the error line) that tuples are immutable.
- Shows a common use case: using tuples (which are hashable) as keys in a dictionary.
Common Mistakes or Pitfalls
- Mutability Confusion: Forgetting whether lists are mutable and tuples are immutable. Trying to change a tuple or expecting list methods like
.append()
to work on tuples. .sort()
vssorted()
: Callingmy_list.sort()
and expecting it to return the sorted list (it returnsNone
and sorts in-place). Use thesorted(my_list)
function to get a new sorted list without changing the original.- IndexError: Accessing a list or tuple index that is out of range.
- ValueError: Using
.remove(value)
whenvalue
is not in the list, or using.index(value)
whenvalue
is not present. Use thein
operator first to check for existence if necessary. - Shallow Copies: Using
new_list = old_list.copy()
creates a shallow copy. If the list contains mutable objects (like other lists), modifying those inner objects will affect both the original and the copy. Deep copies require thecopy
module. - Single-Item Tuple Syntax: Forgetting the trailing comma when defining a tuple with only one item:
my_tuple = (item)
creates just the item, not a tuple; usemy_tuple = (item,)
.
Chapter Summary
- Data structures organize collections of data.
- Lists (
[]
) are ordered, mutable sequences. They can hold items of different types.- Accessed via indexing and slicing.
- Modified using index assignment,
.append()
,.insert()
,.extend()
,.remove()
,.pop()
,del
. - Methods like
.sort()
,.reverse()
,.index()
,.count()
,.copy()
provide further operations.
- Tuples (
()
) are ordered, immutable sequences.- Accessed via indexing and slicing.
- Cannot be changed after creation.
- Useful for fixed data, dictionary keys, function return values.
- Key methods:
.count()
,.index()
. Remember the trailing comma for single-item tuples:(item,)
.
- The
len()
function works on both lists and tuples to get their length. - Tuple unpacking allows assigning sequence items to individual variables (
x, y = my_tuple
).
Exercises & Mini Projects
Exercises
- List Operations: Create a list of numbers, e.g.,
[10, 20, 30, 40, 50]
.- Print the third element.
- Add the number 60 to the end of the list.
- Insert the number 15 at index 1.
- Remove the number 30 from the list.
- Print the final list.
- Tuple Basics: Create a tuple containing the names of the first three months (“January”, “February”, “March”).
- Print the tuple.
- Print the second month using indexing.
- Try to change the first month to “Jan” and observe the
TypeError
.
- List Slicing and Methods: Create a list
letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
.- Print a slice containing ‘c’, ‘d’, ‘e’.
- Print a slice containing the first three letters.
- Use
.pop()
to remove and print the last letter. - Use
.append()
to add the removed letter back. - Reverse the list in-place using
.reverse()
and print it.
- Tuple Unpacking: Create a tuple
record = ("Alice", 30, "Engineer")
. Unpack this tuple into three variables:name
,age
, andjob
. Print each variable on a separate line. - List Membership: Create a list of approved usernames:
approved = ["user1", "admin", "guest"]
. Ask the user to input a username. Use thein
operator and anif-else
statement to print whether the entered username is “Approved” or “Not Approved”.
Mini Project: To-Do List Manager (Simple)
Goal: Create a very basic command-line to-do list manager using a Python list.
Steps:
- Initialize an empty list called
tasks
. - Present the user with a simple menu (using
print
statements):- 1: Add Task
- 2: View Tasks
- 3: Remove Task (by index)
- 4: Exit
- Use an
input()
prompt to get the user’s choice. - Use an
if-elif-else
structure based on the user’s choice:- Choice 1 (Add):
- Ask the user for the task description using
input()
. - Use
.append()
to add the task to thetasks
list. - Print a confirmation message.
- Ask the user for the task description using
- Choice 2 (View):
- Check if the
tasks
list is empty (usingif not tasks:
orif len(tasks) == 0:
). If empty, print “No tasks yet!”. - If not empty, print “— Your Tasks —“.
- Iterate through the list (we’ll cover loops formally soon, but for now, you can manually print elements or just print the whole list:
print(tasks)
). A better way for now: print each task with its index, e.g.,print(f"1: {tasks[0]}")
,print(f"2: {tasks[1]}")
, etc. (This part is tricky without loops, so just printing the list is acceptable for this stage).
- Check if the
- Choice 3 (Remove):
- Check if the list is empty. If so, print “No tasks to remove.”
- If not empty, ask the user for the index (number) of the task to remove. Convert the input to an
int
. - Important: Validate the index. Check if the entered index is valid (e.g.,
0 <= index < len(tasks)
). If invalid, print an error message. - If the index is valid, use
.pop(index)
to remove the task. Print a confirmation message showing the removed task.
- Choice 4 (Exit):
- Print “Exiting program.”
- (We’ll learn how to properly exit loops later; for now, the program will just end after this choice).
- Else (Invalid Choice):
- Print “Invalid choice. Please enter a number between 1 and 4.”
- Choice 1 (Add):
- (Ideally, this would loop, but without loops yet, the program will run once per choice).
(Note: This project anticipates loops, which are covered next. The “View Tasks” and “Remove Task” parts are simplified here. Focus on using list methods correctly based on user input.)
Additional Sources: