Оператор умножения, применяемый к списку (структуре данных)
Я читаю Как думать как компьютерный ученый, который является вводным текстом для "программирования на 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 и добавляет их в список.