Introduction to Lists of Lists
Lists are a fundamental data structure in Python, offering a flexible way to store collections of items. Often, you’ll encounter scenarios where you need to organize data into nested structures, and that’s where lists of lists come into play. A list of lists is simply a list where each element is itself another list. This allows you to represent two-dimensional data like matrices, tables, or any situation where you need a grid-like organization. This tutorial will explore how to create, manipulate, and understand the behavior of lists of lists, with a focus on avoiding common pitfalls related to mutability and references.
Creating Lists of Lists
There are several ways to create a list of lists. Here are a few common methods:
1. Direct Initialization:
The simplest approach is to directly initialize the outer list with inner lists:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(matrix) # Output: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
2. List Comprehension:
List comprehensions provide a concise way to generate lists, including lists of lists:
rows = 3
cols = 4
matrix = [[0 for _ in range(cols)] for _ in range(rows)]
print(matrix) # Output: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
This creates a 3×4 matrix filled with zeros.
3. Using Loops:
You can also create a list of lists using traditional for
loops:
rows = 2
cols = 5
matrix = []
for i in range(rows):
row = []
for j in range(cols):
row.append(i * cols + j) # Example: populate with some values
matrix.append(row)
print(matrix) # Output: [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]
Understanding Mutability and References
One of the most crucial concepts when working with lists of lists is understanding how Python handles mutability and references. Lists are mutable, meaning their contents can be changed after creation. However, assigning one list to another doesn’t create a copy; it creates a new reference to the same list object in memory. This behavior can lead to unexpected results if you’re not careful.
Consider this example:
list1 = [1, 2, 3]
list2 = list1 # list2 now references the same list as list1
list2.append(4)
print(list1) # Output: [1, 2, 3, 4] - list1 is also modified!
print(list2) # Output: [1, 2, 3, 4]
The same issue applies to lists of lists. If you create a list of lists and then assign one of the inner lists to another variable, you’re just creating another reference to the same inner list. Changes made through one reference will be reflected in all other references to that same list.
Creating Copies of Lists
To avoid modifying the original lists unintentionally, you need to create copies. There are several ways to do this:
1. Slicing:
The slicing technique [:]
creates a shallow copy of a list. This is sufficient if the inner lists only contain immutable objects (like numbers or strings).
original_list = [[1, 2], [3, 4]]
copied_list = original_list[:] # Create a shallow copy
copied_list[0][0] = 5 # Modify the copied list
print(original_list) # Output: [[1, 2], [3, 4]] - original list remains unchanged
print(copied_list) # Output: [[5, 2], [3, 4]]
2. list()
Constructor:
The list()
constructor also creates a shallow copy:
original_list = [[1, 2], [3, 4]]
copied_list = list(original_list)
copied_list[0][0] = 5
print(original_list) # Output: [[1, 2], [3, 4]]
print(copied_list) # Output: [[5, 2], [3, 4]]
3. Deep Copy using copy.deepcopy()
:
If your inner lists contain mutable objects (like other lists), you’ll need a deep copy to ensure that all nested objects are also copied independently. The copy.deepcopy()
function from the copy
module does this.
import copy
original_list = [[1, [2, 3]], [4, 5]]
deep_copied_list = copy.deepcopy(original_list)
deep_copied_list[0][1][0] = 6
print(original_list) # Output: [[1, [2, 3]], [4, 5]] - original remains unchanged
print(deep_copied_list) # Output: [[1, [6, 3]], [4, 5]]
Example: Building a List of Lists Incrementally
Let’s illustrate a common scenario: building a list of lists incrementally, while avoiding the reference issue.
data = []
for i in range(3):
row = []
for j in range(4):
row.append(i * 4 + j)
data.append(row[:]) # Append a copy of the row!
data[0][0] = 99
print(data)
# Output: [[99, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]
By appending row[:]
, we create a copy of the row before adding it to the outer list, ensuring that modifications to one row don’t affect the others.