Оператор умножения, применяемый к списку (структуре данных)

Я читаю Как думать как компьютерный ученый, который является вводным текстом для "программирования на Python".

Я хочу уточнить поведение оператора multiply (*) при применении к спискам.

Рассмотрим функцию make_matrix

def make_matrix(rows, columns):
"""
  >>> make_matrix(4, 2)
  [[0, 0], [0, 0], [0, 0], [0, 0]]
  >>> m = make_matrix(4, 2)
  >>> m[1][1] = 7
  >>> m
  [[0, 0], [0, 7], [0, 0], [0, 0]]
"""
return [[0] * columns] * rows

Фактический вывод

[[0, 7], [0, 7], [0, 7], [0, 7]]

Правильная версия make_matrix:

def make_matrix(rows, columns):
"""
  >>> make_matrix(3, 5)
  [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
  >>> make_matrix(4, 2)
  [[0, 0], [0, 0], [0, 0], [0, 0]]
  >>> m = make_matrix(4, 2)
  >>> m[1][1] = 7
  >>> m
  [[0, 0], [0, 7], [0, 0], [0, 0]]
"""
matrix = []
for row in range(rows):
    matrix += [[0] * columns]
return matrix

Причина, по которой первая версия make_matrix терпит неудачу (как поясняется в книге в 9.8), заключается в том, что

... каждая строка является псевдонимом других строк...

Интересно, почему

[[0] * columns] * rows

вызывает... каждая строка является псевдонимом других строк...

но не

[[0] * columns]

то есть. почему каждый [0] в строке не является псевдонимом другого элемента строки.

Ответ 1

ВСЕ в python являются объектами, и python никогда не делает копии, если только объяснение не потребовало этого.

Когда вы делаете

innerList = [0] * 10

вы создаете список из 10 элементов, , все из которых ссылаются на один и тот же объект int 0.

Так как целые объекты неизменяемы, когда вы делаете

innerList[1] = 15

Вы меняете второй элемент списка так, чтобы он ссылался на другое целое число 15. Это всегда работает из-за неизменности объектов int.

Вот почему

outerList = innerList * 5

Создает объект list с 5 элементами, каждый из которых ссылается на тот же innerList, что и выше. Но поскольку list объекты изменяемы:

outerList[2].append('something')

То же, что и:

innerList.append('something')

Потому что это две ссылки на тот же list объект. Таким образом, элемент заканчивается в этом одиночном list. Кажется, он дублируется, но факт в том, что существует только один объект list, и многие ссылки на него.

В отличие от этого, если вы делаете

outerList[1] = outerList[1] + ['something']

Здесь вы создаете еще один объект list (используя + со списками - явная копия) и назначая ссылку на него во вторую позицию outerList. Если вы "добавите" этот элемент таким образом (на самом деле не добавляете, но создаете другой список), innerList не будет затронут.

Ответ 2

списки не являются примитивами, они передаются по ссылке. Копия списка является указателем на список (на языке жаргонов). Все, что вы делаете в списке, происходит со всеми копиями списка и копиями его содержимого, если вы не сделаете мелкую копию.

[[0] * columns] * rows

К сожалению, мы сделали большой список указателей на [0]. Измените его, и вы все измените.

Целые числа не передаются по ссылке, они действительно скопированы, поэтому содержимое [0] * действительно делает много NEW 0 и добавляет их в список.