Nested Lists, Copying and List Patterns
Lists of lists model grids and tables, copying them needs care, and a handful of patterns cover most real list work.
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
Nested lists
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
print(matrix[1]) # [4, 5, 6] the second row
print(matrix[1][2]) # 6 row 1, column 2
print(len(matrix)) # 3 number of rows
print(len(matrix[0])) # 3 number of columnsRead matrix[row][column] left to right: take the row, then index into it.
Building a grid correctly
rows, columns = 3, 4
# WRONG - every row is the same list object
grid = [[0] * columns] * rows
grid[0][0] = 9
print(grid) # [[9, 0, 0, 0], [9, 0, 0, 0], [9, 0, 0, 0]]
# CORRECT - the comprehension builds a fresh row each time
grid = [[0] * columns for _ in range(rows)]
grid[0][0] = 9
print(grid) # [[9, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]The inner [0] * columns is safe because integers are immutable. Only the outer repetition is dangerous, because it duplicates references to one mutable row.Walking a grid
matrix = [[1, 2, 3], [4, 5, 6]]
for row in matrix:
for value in row:
print(value, end=" ")
print()
for r, row in enumerate(matrix):
for c, value in enumerate(row):
print(f"({r},{c})={value}", end=" ")
print()
# Row and column totals
print([sum(row) for row in matrix]) # [6, 15]
print([sum(column) for column in zip(*matrix)]) # [5, 7, 9]Transposing
matrix = [[1, 2, 3], [4, 5, 6]]
print(list(zip(*matrix))) # [(1, 4), (2, 5), (3, 6)]
print([list(row) for row in zip(*matrix)]) # [[1, 4], [2, 5], [3, 6]]
# The same thing with explicit loops
transposed = [[matrix[r][c] for r in range(len(matrix))] for c in range(len(matrix[0]))]
print(transposed)zip(*matrix) unpacks the rows as separate arguments to zip, which then pairs them position by position. It is the standard Python transpose.
Flattening
nested = [[1, 2], [3, 4], [5]]
flat = [value for row in nested for value in row]
print(flat) # [1, 2, 3, 4, 5]
# The loop order in a comprehension reads exactly like nested for loops:
flat = []
for row in nested:
for value in row:
flat.append(value)import itertools
print(list(itertools.chain.from_iterable(nested))) # [1, 2, 3, 4, 5]Flattening to any depth
def flatten(items):
result = []
for item in items:
if isinstance(item, list):
result.extend(flatten(item))
else:
result.append(item)
return result
print(flatten([1, [2, [3, [4, 5]]], 6])) # [1, 2, 3, 4, 5, 6]Shallow versus deep copy
import copy
original = [[1, 2], [3, 4]]
shallow = original.copy()
deep = copy.deepcopy(original)
original[0][0] = 99
print(original) # [[99, 2], [3, 4]]
print(shallow) # [[99, 2], [3, 4]] <- the inner list is shared
print(deep) # [[1, 2], [3, 4]] <- fully independentshallow copy deep copy
original ─┐ original ──► [ ref, ref ] ──► [1,2] [3,4]
├──► [1,2]
shallow ──┘ deep ──────► [ ref, ref ] ──► [1,2] [3,4]
(new objects)A shallow copy duplicates the outer list only. If every element is immutable, that is enough. If any element is mutable and might be changed, you need deepcopy.
Common list patterns
Filtering
numbers = [4, -2, 7, 0, -5, 9]
positives = [n for n in numbers if n > 0]
print(positives) # [4, 7, 9]
evens = list(filter(lambda n: n % 2 == 0, numbers))
print(evens) # [4, -2, 0]Transforming
words = [" apple ", "BANANA", "Cherry "]
cleaned = [w.strip().lower() for w in words]
print(cleaned) # ['apple', 'banana', 'cherry']
lengths = list(map(len, cleaned))
print(lengths) # [5, 6, 6]Removing duplicates
items = [3, 1, 3, 2, 1]
print(list(set(items))) # order not preserved
print(list(dict.fromkeys(items))) # [3, 1, 2] - order preserved
# Manually, when the items are not hashable
seen = []
for item in items:
if item not in seen:
seen.append(item)
print(seen)dict.fromkeys is the standard trick for deduplicating while keeping the first occurrence order, because dictionaries preserve insertion order.
Chunking
items = list(range(1, 11))
size = 3
chunks = [items[i:i + size] for i in range(0, len(items), size)]
print(chunks) # [[1,2,3], [4,5,6], [7,8,9], [10]]Finding
people = [
{"name": "Meera", "age": 27},
{"name": "Arun", "age": 31},
]
match = next((p for p in people if p["name"] == "Arun"), None)
print(match) # {'name': 'Arun', 'age': 31}
missing = next((p for p in people if p["name"] == "Zara"), None)
print(missing) # Nonenext(generator, default) stops at the first match instead of building a whole filtered list, and the default keeps it from raising when nothing matches.
Grouping
words = ["apple", "avocado", "banana", "blueberry", "cherry"]
groups = {}
for word in words:
groups.setdefault(word[0], []).append(word)
print(groups)
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}Running totals
import itertools
sales = [100, 250, 75, 300]
print(list(itertools.accumulate(sales))) # [100, 350, 425, 725]Common mistakes
- Building a grid with
[[0] * n] * m. - Assuming
.copy()protects nested data. - Reversing the index order and writing
matrix[column][row]. - Using
set()to deduplicate when the original order matters. - Writing a nested comprehension with the loops in the wrong order.
- Assuming all rows are the same length when the data came from a file.
Best practices
- Build nested structures with comprehensions, never with repetition.
- Use
deepcopyonly when you need it; it is slow and it copies everything. - Use
zip(*matrix)to transpose anddict.fromkeysto deduplicate. - Once a grid grows past a few operations, consider whether a dictionary keyed by coordinates reads better.
Practice
- Write a function that returns the sum of each row and each column of a matrix.
- Rotate a square matrix by 90 degrees using
zipand slicing. - Demonstrate the difference between
copy()anddeepcopy()on a list of lists in five lines. - Split a list of 23 items into chunks of 5 and report the size of the last chunk.
- Group a list of names by their length into a dictionary.
Conclusion
Nested lists are lists of references, which is why grids must be built with a comprehension and why copying them needs deepcopy. Beyond that, most list work is one of a few patterns: filter, transform, deduplicate, chunk, find and group.