2013-07-17 3 views
11

Я уверен, что на это был дан ответ, но я не был уверен, как его описать.Создание подписок с использованием умножения (*) неожиданное поведение

Скажем, я хочу, чтобы создать список, содержащий 3 пустых списков, например, так:

lst = [[], [], []] 

Я думал, что я был все умные, делая это:

lst = [[]] * 3 

Но я обнаружил, после того, как отлаживая какое-то странное поведение, что вызвало обновление приложения одному подписок, скажем lst[0].append(3), чтобы обновить весь список, сделав его [[3], [3], [3]], а не [[3], [], []].

Однако, если я инициализировать список с

lst = [[] for i in range(3)] 

затем делать lst[1].append(5) дает ожидаемый [[], [5], []]

Мой вопрос почему это происходит? Интересно отметить, что если я

lst = [[]]*3 
lst[0] = [5] 
lst[0].append(3) 

то «связь» ячейки 0 нарушается, и я получаю [[5,3],[],[]], но lst[1].append(0) до сих пор вызывает [[5,3],[0],[0].

Мое лучшее предположение заключается в том, что использование умножения в форме [[]]*x заставляет Python хранить ссылку на одну ячейку ...?

+0

@RoadieRich Но ответы здесь содержат более подробное объяснение и ссылки на официальную документацию. –

+1

@AseemBansal: Тогда эти ответы должны быть добавлены к * другому * вопросу, а может быть? –

+0

Это окончательно дубликат. http://stackoverflow.com/questions/240178/unexpected-feature-in-a-python-list-of-lists и http://stackoverflow.com/questions/1605024/python-using-the-multiply-operator- to-create-copy-of-objects-in-lists и http://stackoverflow.com/questions/6688223/python-list-multiplication-3-makes-3-lists-which-mirror-each-other-when? lq = 1 и другие, которых я не могу найти сейчас. –

ответ

19

Мое лучшее предположение заключается в том, что использование умножения в форме [[]] * x заставляет Python хранить ссылку на одну ячейку ...?

Да. И вы можете проверить это сами

>>> lst = [[]] * 3 
>>> print [id(x) for x in lst] 
[11124864, 11124864, 11124864] 

Это показывает, что все три ссылки относятся к одному и тому же объекту. И обратите внимание, что это действительно имеет смысл, что это происходит . Он просто копирует значения , и в этом случае значения являются ссылками. И поэтому вы видите одну и ту же рекомендацию трижды.

Интересно отметить, что если я

lst = [[]]*3 
lst[0] = [5] 
lst[0].append(3) 

то 'связь' ячейки 0 нарушается, и я получаю [[5,3],[],[]], но lst[1].append(0) до сих пор вызывает [[5,3],[0],[0].

Вы изменили ссылку, которая занимает lst[0]; то есть вы назначили новое значение - lst[0].Но вы не изменили значение значения других элементов, они все еще ссылаются на тот же объект, на который они ссылались. И lst[1] и lst[2] по-прежнему относятся к одному и тому же экземпляру, поэтому, конечно, добавление предмета в lst[1] вызывает lst[2], чтобы увидеть это изменение.

Это классическая ошибка, которую люди делают с помощью указателей и ссылок. Вот простая аналогия. У вас есть лист бумаги. На этом вы пишете адрес чей-то дома. Теперь вы берете этот лист бумаги и копируете его дважды, так что в итоге вы получаете три листа бумаги с тем же адресом, написанным на них. Теперь возьмите сначала лист бумаги, напишите адрес, написанный на нем, и напишите новый адрес кому-то еще дом. Изменился ли адрес, записанный на двух других листах бумаги? Нет. Это точно что сделал ваш код. Это почему другие два элемента не меняются. Далее, представьте себе, что владелец дома с адресом все еще на втором листе бумаги создает дополнительный гараж для своего дома. Теперь я спрашиваю вас, имеет ли дом, адрес которого находится на третьем листом бумаги есть дополнительный гараж? Да, это так, потому что это точно тот же дом, что и тот, чей адрес написан на второй лист бумаги. Это объясняет все о вашем втором примере кода.

: Вы не ожидали, что Python вызовет «конструктор копирования», не так ли? Пук.

+1

+1 для 'id()'. Это будет полезно. –

+0

Спасибо за 'id (x)', а сборка гаража - хороший пример для редактирования указателя. –

5

Это потому, что умножение последовательности просто повторяет ссылки. Когда вы пишете [[]] * 2, вы создаете новый список с двумя элементами, но оба эти элемента: тот же объект в памяти, а именно пустой список. Следовательно, изменение в одном отражается в другом. Понимание, напротив, создает новый, независимый список на каждой итерации:

>>> l1 = [[]] * 2 
>>> l2 = [[] for _ in xrange(2)] 
>>> l1[0] is l1[1] 
True 
>>> l2[0] is l2[1] 
False 
+0

Я не думал попробовать оператора «есть», спасибо! –

+0

@AdrianWan Нет проблем, рад, что я мог бы помочь. – arshajii

5

Они ссылающийся те же списки.

Есть подобные вопросы here и here

И от FAQ:

«* не создавать копии, он только создает ссылки на существующие объектов.»

+0

+1 для ссылки на документы Python. –

1

Ваше предположение, что использование умножения в форме [[]] * x заставляет Python хранить ссылку на одну ячейку правильно.

Итак, вы получите список из 3 ссылок на один список.

1

В основном то, что происходит в вашем первом примере, состоит в том, что список создается с несколькими ссылками на один и тот же внутренний список. Вот разбивка.

>>> a = [] 
>>> b = [a] 
>>> c = b * 3 # c now contains three references to a 
>>> d = [ a for _ in xrange(4) ] # and d contains four references to a 
>>> print c 
[[], [], []] 
>>> print d 
[[], [], [], []] 
>>> a.append(3) 
>>> print c 
[[3], [3], [3]] 
>>> print d 
[[3], [3], [3], [3]] 
>>> x = [[]] * 3 # shorthand equivalent to c 
>>> print x 
[[], [], []] 
>>> x[0].append(3) 
>>> print x 
[[3], [3], [3]] 

Вышеупомянутое соответствует вашему первому примеру. Теперь, когда каждому списку дается собственная переменная, мы надеемся, что более понятно почему.c[0] is c[1] будет оцениваться как True, так как оба выражения оцениваются одним и тем же объектом (a).

Ваш второй пример создает несколько разных объектов внутреннего списка.

>>> c = [[], [], []] # this line creates four different lists 
>>> d = [ [] for _ in xrange(3) ] # so does this line 
>>> c[0].append(4) 
>>> d[0].append(5) 
>>> print c 
[[4], [], []] 
>>> print d 
[[5], [], []] 
Смежные вопросы