Оператор умножения применен к списку (структура данных)
-
13-09-2019 - |
Вопрос
я читаю Как думать как ученый-компьютерщик это вводный текст по «Программированию на Python».
Я хочу уточнить поведение оператора умножения (*
) при применении к спискам.
Рассмотрим функцию 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]
в строке не является псевдонимом другого элемента строки.
Решение
ВСЕ в 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
не будет затронуто.
Другие советы
списки не являются примитивами, они передаются по ссылке.Копия списка — это указатель на список (на жаргоне C).Все, что вы делаете со списком, происходит со всеми копиями списка и копиями его содержимого, если только вы не сделаете поверхностное копирование.
[[0] * columns] * rows
Упс, мы только что составили большой список указателей на [0].Измените одно, и вы измените их все.
Целые числа не передаются по ссылке, они на самом деле копируются, поэтому содержимое [0] * на самом деле создает множество НОВЫХ 0 и добавляет их в список.