Список змін списків, несподівано відображених у списках списків


645

Мені потрібно було створити список списків у Python, тому я набрав таке:

myList = [[1] * 4] * 3

Список виглядав так:

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]  

Тоді я змінив одне із найпотужніших значень:

myList[0][0] = 5

Тепер мій список виглядає так:

[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]  

що не є те, чого я хотів чи очікував. Може хтось, будь ласка, пояснить, що відбувається, і як це обійти?

Відповіді:


560

Коли ви пишете, [x]*3ви отримуєте, по суті, список [x, x, x]. Тобто список із 3 посиланнями на той самий x. Після цього ви модифікуєте цей сингл, xвін видно через усі три посилання на нього:

x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(l[0]): {id(l[0])}\n"
    f"id(l[1]): {id(l[1])}\n"
    f"id(l[2]): {id(l[2])}"
)
# id(l[0]): 140560897920048
# id(l[1]): 140560897920048
# id(l[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

Щоб виправити це, вам потрібно переконатися, що ви створюєте новий список на кожній позиції. Один із способів це зробити

[[1]*4 for _ in range(3)]

яка буде переоцінювати [1]*4кожного разу замість того, щоб оцінювати її один раз і робити 3 посилання на 1 список.


Вам може бути цікаво, чому *не можна робити незалежні об’єкти так, як це робить розуміння списку. Це тому, що оператор множення *працює над об'єктами, не бачачи виразів. Коли ви використовуєте *для множення [[1] * 4]на 3, *бачить лише 1-елементний список, який [[1] * 4]оцінює, а не [[1] * 4текст виразу. *не має ідеї, як зробити копії цього елемента, не має ідеї, як переоцінити [[1] * 4], і немає ідеї, що ви навіть хочете копії, і взагалі, може навіть не бути способу копіювання елемента.

Єдиний варіант *полягає в тому, щоб робити нові посилання на існуючий підспис, а не намагатися вносити нові списки. Все інше було б суперечливим або вимагало б значного перероблення основних мовних дизайнерських рішень.

Навпаки, розуміння списку переоцінює вираз елемента на кожній ітерації. [[1] * 4 for n in range(3)]переоцінюється [1] * 4кожен раз з тієї ж причини, щоразу [x**2 for x in range(3)]переоцінюється x**2. Кожна оцінка [1] * 4генерує новий список, тому розуміння списку робить те, що ви хотіли.

Між іншим, [1] * 4також не копіює елементи [1], але це не має значення, оскільки цілі числа незмінні. Ви не можете зробити щось на кшталт 1.value = 2і перетворити 1 на 2.


24
Я здивований, що жоден орган не вказує на це, відповідь тут хибна. [x]*3зберігати 3 посилання, як [x, x, x]це правильно, лише коли xвін змінений. Це не працює, наприклад a=[4]*3, де після a[0]=5,a=[5,4,4].
Allanqunzi

42
Технічно це все-таки правильно. [4]*3по суті еквівалентний x = 4; [x, x, x]. Однак правда, що це ніколи не спричинить жодних проблем, оскільки 4незмінне. Крім того, ваш інший приклад насправді не інший випадок. a = [x]*3; a[0] = 5не спричинить проблем, навіть якщо xвін змінений, оскільки ви не змінюєте x, а лише модифікуєте a. Я б не назвав свою відповідь оманливою чи неправильною - ви просто не можете стріляти собі в ногу, якщо маєте справу з незмінними предметами.
CAdaker

19
@Allanqunzi ви помиляєтесь. Зробіть x = 1000; lst = [x]*2; lst[0] is lst[1]-> True. Python тут не розрізняє змінні та незмінні об'єкти.
timgeb

129
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Кадри та об'єкти

Live Python Tutor Візуалізуйте


Отже, чому, якщо ми пишемо матриця = [[x] * 2] не робить 2 елементи для того ж об'єкта, як приклад, який ти описуєш, це, здається, те саме поняття, чого я пропускаю?
Ахмед Мохамед

@AhmedMohamed Дійсно, він робить список з двома елементами точно такого ж об'єкта, на який xпосилається. Якщо ви зробите унікальний глобальний об'єкт, x = object()а потім зробите matrix = [[x] * 2]це, як справді:matrix[0][0] is matrix[0][1]
nadrimajstor

@nadrimajstor, тож чому зміна матриці [0] не впливає на матрицю [1], як у наведеному вище прикладі з 2d матрицею.
Ахмед Мохамед

@AhmedMohamed сюрприз приходить, коли ви робите "копію" змінної послідовності (у нашому прикладі це а list), тож якщо row = [x] * 2a, matrix = [row] * 2де обидва ряди - це точно той самий об'єкт, а тепер зміни в одному ряду matrix[0][0] = yраптово відображаються в іншому(matrix[0][0] is matrix[1][0]) == True
nadrimajstor

@AhmedMohamed Погляньте на Неда Батчелдера - Факти та міфи про імена та значення Python, оскільки це може запропонувати краще пояснення. :)
nadrimajstor

52

Власне, саме цього ви і очікували. Розберемо, що тут відбувається:

Ви пишете

lst = [[1] * 4] * 3

Це еквівалентно:

lst1 = [1]*4
lst = [lst1]*3

Це означає lstсписок із 3-ма елементами, на які всі вказують lst1. Це означає, що два наступні рядки рівнозначні:

lst[0][0] = 5
lst1[0] = 5

Як lst[0]не що інше lst1.

Щоб отримати бажану поведінку, ви можете використовувати розуміння списку:

lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2

У цьому випадку вираз переоцінюється для кожного n, ведучи до іншого списку.


Лише невелике доповнення до приємної відповіді тут: очевидно, що ти маєш справу з одним і тим же об’єктом, якщо ти робиш id(lst[0][0])і id(lst[1][0])навіть id(lst[0])і навітьid(lst[1])
Сергій Колодяжний,

36
[[1] * 4] * 3

або навіть:

[[1, 1, 1, 1]] * 3

Створюється список, який посилається на внутрішній [1,1,1,1]3 рази - не три копії внутрішнього списку, тому щоразу, коли ви модифікуєте список (у будь-якій позиції), ви побачите зміни три рази.

Це те саме, що і цей приклад:

>>> inner = [1,1,1,1]
>>> outer = [inner]*3
>>> outer
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
>>> inner[0] = 5
>>> outer
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

де це, мабуть, трохи менш дивно.


3
Ви можете скористатися оператором "є", щоб виявити це. ls [0] є ls [1] повертає True.
mipadi

9

Поряд із прийнятою відповіддю, яка правильно пояснила проблему, в межах вашого розуміння списку, якщо ви використовуєте python-2.x use, xrange()який повертає генератор, який є більш ефективним ( range()у python 3 виконує ту саму роботу) _замість змінної, що викидає n:

[[1]*4 for _ in xrange(3)]      # and in python3 [[1]*4 for _ in range(3)]

Крім того, як набагато більш пітонічний спосіб ви можете використовувати itertools.repeat()для створення об’єкта ітератора повторних елементів:

>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]

PS Використовуючи numpy, якщо ви хочете створити лише масив з одиниць або нулів, який ви можете використовувати np.onesта np.zerosта / або використовувати для іншого числа np.repeat():

In [1]: import numpy as np

In [2]: 

In [2]: np.ones(4)
Out[2]: array([ 1.,  1.,  1.,  1.])

In [3]: np.ones((4, 2))
Out[3]: 
array([[ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.]])

In [4]: np.zeros((4, 2))
Out[4]: 
array([[ 0.,  0.],
       [ 0.,  0.],
       [ 0.,  0.],
       [ 0.,  0.]])

In [5]: np.repeat([7], 10)
Out[5]: array([7, 7, 7, 7, 7, 7, 7, 7, 7, 7])

6

Контейнери Python містять посилання на інші об'єкти. Дивіться цей приклад:

>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]

У цьому bсписку міститься один елемент, який є посиланням на список a. Список aє змінним.

Множення списку на ціле число еквівалентно додаванню списку до себе кілька разів (див. Загальні операції послідовності ). Отже, продовжуючи приклад:

>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]

Ми можемо бачити, що cзараз список містить дві посилання на список, aщо еквівалентно c = b * 2.

Поширені питання Python також містять пояснення такої поведінки: Як створити багатовимірний список?


6

myList = [[1]*4] * 3створює один об'єкт списку [1,1,1,1]в пам'яті і копіює його посилання 3 рази. Це еквівалентно obj = [1,1,1,1]; myList = [obj]*3. Будь-яка зміна objбуде відображена у трьох місцях, де б objне було посилання у списку. Правильним твердженням було б:

myList = [[1]*4 for _ in range(3)]

або

myList = [[1 for __ in range(4)] for _ in range(3)]

Тут важливо зазначити , що *оператор використовується в основному для створення списку літералів . Незважаючи на те, що 1він незмінний, obj =[1]*4він все одно створить список, 1повторюваний 4 рази, щоб сформувати [1,1,1,1]. Але якщо робиться якесь посилання на незмінний об’єкт, об'єкт перезаписується новим.

Це означає, що якщо ми це зробимо obj[1]=42, то objстанемо [1,42,1,1] не так, [42,42,42,42] як деякі можуть припустити. Це також можна перевірити:

>>> myList = [1]*4
>>> myList
[1, 1, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # Same as myList[0]
4522139440

>>> myList[1] = 42 # Since myList[1] is immutable, this operation overwrites myList[1] with a new object changing its id.
>>> myList
[1, 42, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # id changed
4522140752
>>> id(myList[2]) # id still same as myList[0], still referring to value `1`.
4522139440

2
Мова не йде про літерали. obj[2] = 42 замінює посилання на індекс 2, на відміну від мутації об'єкта, на який посилається цей індекс, що і myList[2][0] = ...робить ( myList[2]це список, і призначення змінює посилання на індекс 0 у списку tha). Звичайно, цілі числа не змінюються, але типи об'єктів є безліч . І зауважте, що [....]позначення відображення списку також є формою буквального синтаксису! Не плутайте складені (наприклад, списки) та скалярні об'єкти (наприклад, цілі числа), із об'єктами, що змінюються, порівняно з незмінними.
Martijn Pieters

5

Простими словами, це відбувається тому, що в python все працює за посиланням , тому коли ви створюєте список списку таким чином, ви, в основному, стикаєтеся з такими проблемами.

Щоб вирішити свою проблему, ви можете виконати будь-яку з них: 1. Використовуйте документацію масиву numpy для numpy.empty 2. Додайте список, як потрапите до списку. 3. Ви також можете використовувати словник, якщо хочете


2

Давайте перепишемо ваш код наступним чином:

x = 1
y = [x]
z = y * 4

myList = [z] * 3

Після цього, запустіть наступний код, щоб зробити все більш зрозумілим. Що код робить, це в основному друкувати ids отриманих об'єктів, які

Поверніть "ідентичність" об'єкта

і допоможе нам визначити їх і проаналізувати, що відбувається:

print("myList:")
for i, subList in enumerate(myList):
    print("\t[{}]: {}".format(i, id(subList)))
    for j, elem in enumerate(subList):
        print("\t\t[{}]: {}".format(j, id(elem)))

І ви отримаєте такий вихід:

x: 1
y: [1]
z: [1, 1, 1, 1]
myList:
    [0]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [1]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [2]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528

Тож тепер відпустимо крок за кроком. У вас є, xякий є 1, і єдиний список елементів, yщо містить x. Ваш перший крок полягає в тому, y * 4що ви отримаєте новий список z, який в основному є [x, x, x, x], тобто він створює новий список, який буде мати 4 елементи, які є посиланнями на початковий xоб'єкт. Чистий крок досить схожий. Ви в основному робите z * 3, що є, [[x, x, x, x]] * 3і повертаєтеся [[x, x, x, x], [x, x, x, x], [x, x, x, x]]з тієї ж причини, що і для першого кроку.


2

Я думаю, всі пояснюють, що відбувається. Я пропоную один із способів вирішити:

myList = [[1 for i in range(4)] for j in range(3)]

myList[0][0] = 5

print myList

І тоді у вас є:

[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

2

Намагаючись пояснити це більш описово,

Операція 1:

x = [[0, 0], [0, 0]]
print(type(x)) # <class 'list'>
print(x) # [[0, 0], [0, 0]]

x[0][0] = 1
print(x) # [[1, 0], [0, 0]]

Операція 2:

y = [[0] * 2] * 2
print(type(y)) # <class 'list'>
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [1, 0]]

Помічено, чому зміна першого елемента першого списку не змінила другий елемент кожного списку? Це тому, що [0] * 2справді є список з двох чисел, і посилання на 0 неможливо змінити.

Якщо ви хочете створити копії клонів, спробуйте операцію 3:

import copy
y = [0] * 2   
print(y)   # [0, 0]

y = [y, copy.deepcopy(y)]  
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [0, 0]]

ще один цікавий спосіб створення клонових копій, Операція 4:

import copy
y = [0] * 2
print(y) # [0, 0]

y = [copy.deepcopy(y) for num in range(1,5)]
print(y) # [[0, 0], [0, 0], [0, 0], [0, 0]]

y[0][0] = 5
print(y) # [[5, 0], [0, 0], [0, 0], [0, 0]]

2

@spelchekr з множення списку Python: [[...]] * 3 робить 3 списки, які відображають один одного при зміні, і у мене виникло те саме питання про "Чому лише зовнішній * 3 створює більше посилань, тоді як внутрішній не робить "Чому це не всі?"

li = [0] * 3
print([id(v) for v in li]) # [140724141863728, 140724141863728, 140724141863728]
li[0] = 1
print([id(v) for v in li]) # [140724141863760, 140724141863728, 140724141863728]
print(id(0)) # 140724141863728
print(id(1)) # 140724141863760
print(li) # [1, 0, 0]

ma = [[0]*3] * 3 # mainly discuss inner & outer *3 here
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
ma[0][0] = 1
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
print(ma) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

Ось моє пояснення після спроби коду вище:

  • Внутрішня *3також створює посилання, але це посилання незмінні, щось подібне [&0, &0, &0], тоді, коли потрібно змінити li[0], ви не можете змінити жодної основної посилання const int 0, тому ви можете просто змінити адресу посилання на нову &1;
  • в той час як ma=[&li, &li, &li]і liє змінним, тому коли ви телефонуєте ma[0][0]=1, ma [0] [0] дорівнює &li[0], тому всі &liекземпляри змінять свою першу адресу на &1.

1

За допомогою функції вбудованого списку ви можете зробити так

a
out:[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#Displaying the list

a.remove(a[0])
out:[[1, 1, 1, 1], [1, 1, 1, 1]]
# Removed the first element of the list in which you want altered number

a.append([5,1,1,1])
out:[[1, 1, 1, 1], [1, 1, 1, 1], [5, 1, 1, 1]]
# append the element in the list but the appended element as you can see is appended in last but you want that in starting

a.reverse()
out:[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#So at last reverse the whole list to get the desired list

1
Зауважте, що четвертий крок можна відмовити, якщо ви зробите другий крок:a.insert(0,[5,1,1,1])
U10-Вперед
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.