A Python Oddity


The Oddity

Let’s say you want to intialize a matrix (a two-dimensional list) in python. It’ll be an NxN matrix of numbers. Just for fun we’ll make all the inital elements 1, instead of 0.

One might think the following would be a sensible one-liner.

# sample.py
def print_mat(matrix):
    for row in matrix:
        print(row)

matrix = [[1] * 10] * 10
print_mat(matrix)

prints

$ python3 sample.py
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

But, what happens if we modify part of the matrix?

# sample.py
# same print_mat as above
matrix = [[1] * 10] * 10
print("--- BEFORE MOD ---")
print_mat(matrix)
matrix[0][3] = 89
print()
print("--- AFTER MOD ---")
print_mat(matrix)

prints

$ python3 sample.py
--- BEFORE MOD ---
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

--- AFTER MOD ---
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]

Oops! That clearly creates a list containing ten of the same list.

(If you are confused, we would normally expect only the first row to have been modified to have the 89, not all of the rows)

Explanation

To better illustrate what’s going on, the above declaration of matrix

matrix = [[1] * 10] * 10

is really the same as

inner_list = [1] * 10
matrix = [inner_list] * 10

Since python variables are really just pointers to objects, we’re making a list of ten of the same pointer to one object, in this case our list of ten ones.

The Correct Way

It’s pretty straight forward to fix this, we just use a list comprehension instead of the * shorthand, which produces this unexpected behavior when applied to a list of lists.

# sample.py
matrix = [[1] * 10 for _ in range(10)]
print("--- BEFORE MOD ---")
print_mat(matrix)
matrix[0][3] = 89
print()
print("--- AFTER MOD ---")
print_mat(matrix)

which produces the expected output

$ python3 sample.py
--- BEFORE MOD ---
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

--- AFTER MOD ---
[1, 1, 1, 89, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

Note: we could also say matrix = [[1 for _ in range(10)] for _ in range(10)], but that is more to type!