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.

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 columns

Read 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 independent
shallow 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)      # None

next(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 deepcopy only when you need it; it is slow and it copies everything.
  • Use zip(*matrix) to transpose and dict.fromkeys to deduplicate.
  • Once a grid grows past a few operations, consider whether a dictionary keyed by coordinates reads better.

Practice

  1. Write a function that returns the sum of each row and each column of a matrix.
  2. Rotate a square matrix by 90 degrees using zip and slicing.
  3. Demonstrate the difference between copy() and deepcopy() on a list of lists in five lines.
  4. Split a list of 23 items into chunks of 5 and report the size of the last chunk.
  5. 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.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Python notes →
Python

Sorting Algorithms

Python sorts for you in n log n. Implementing bubble, insertion, merge and quick sort is still worth doing, because it teaches how algorithms are comp...

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.