Почему использование оператора умножения в списке создает список указателей?
>>> rows = [['']*5]*5
>>> rows
[['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', '']]
>>> rows[0][0] = 'x'
Естественно, я ожидаю, что строки станут:
[['x', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', ''], ['', '', '', '', '']]
Вместо этого я получаю:
[['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', ''], ['x', '', '', '', '']]
Кажется, что элементы списка строк являются указателями на один и тот же старый список [''] * 5. Почему он работает таким образом и является ли это функцией Python?
Ответы
Ответ 1
Поведение не характерно для оператора повторения (*
). Например, если вы объединяете два списка с помощью +
, поведение будет одинаковым:
In [1]: a = [[1]]
In [2]: b = a + a
In [3]: b
Out[3]: [[1], [1]]
In [4]: b[0][0] = 10
In [5]: b
Out[5]: [[10], [10]]
Это связано с тем, что списки - это объекты, а объекты хранятся по ссылке. Когда вы используете *
et al, эта ссылка повторяется, следовательно, поведение, которое вы видите.
Ниже показано, что все элементы rows
имеют одинаковый идентификатор (т.е. адрес памяти в CPython):
In [6]: rows = [['']*5]*5
In [7]: for row in rows:
...: print id(row)
...:
...:
15975992
15975992
15975992
15975992
15975992
Следующий пример эквивалентен вашему примеру, за исключением того, что он создает пять различных списков для строк:
rows = [['']*5 for i in range(5)]
Ответ 2
Тот факт, что имена, функциональные параметры и контейнеры имеют ссылочную семантику, является очень простым дизайнерским решением в Python. Это влияет на то, как Python работает во многих аспектах, и вы выбрали только один из этих аспектов. Во многих случаях эталонная семантика более удобна, тогда как в других случаях копии были бы более удобными. В Python вы всегда можете явно создать копию, если это необходимо, или, в этом случае, вместо этого используйте понимание списка:
rows = [[''] * 5 for i in range(5)]
Вы могли бы разработать язык программирования с другой семантикой, и есть много языков, которые имеют разную семантику, а также языки с аналогичной семантикой. Почему это решение было принято, немного сложно ответить - язык просто должен иметь некоторую семантику, и вы всегда можете спросить, почему. Вы могли бы также спросить, почему Python динамически типизирован, и, в конце концов, ответ заключается в том, что именно это было решено Guido еще в 1989 году.
Ответ 3
Вы правы, что Python использует указатели "под капотом", и да, это особенность. Я не знаю точно, почему они это сделали way- Я предполагаю, что это было для скорости и сокращения использования памяти.
Кстати, этот вопрос, кстати, почему важно понимать различие между мелкой копией и глубокими копиями.