Обновление списка

Мне показалось, что я понял операции нарезания Python, но когда я попытался обновить список, я смутился:

>>> foo = [1, 2, 3, 4]
>>> foo[:1] = ['one'] # OK, foo updated
>>> foo
['one', 2, 3, 4] 
>>> foo[:][1] = 'two' # why foo not updated?
>>> foo
['one', 2, 3, 4] 
>>> foo[:][2:] = ['three', 'four'] # Again, foo not updated
>>> foo
['one', 2, 3, 4] 

Почему обновление не обновляется после foo[:][1] = 'two'?

Обновление: Возможно, я не ясно объяснил свои вопросы. Я знаю, что при разрезании создается новый список. Я сомневаюсь, почему назначение разреза обновляет список (например, foo[:1] = ['one']), но если есть два уровня среза, он не обновляет исходный список (например, foo[:][2:] = ['three', 'four']).

Ответы

Ответ 1

Это связано с тем, что python не имеет l-значения, которые могут быть назначены. Вместо этого некоторые выражения имеют форму присваивания, которая отличается.

A foo[something] - синтаксический сахар для:

foo.__getitem__(something)

но a foo[something] = bar является синтаксическим сахаром для довольно разных:

foo.__setitem__(something, bar)

Если срез представляет собой только частный случай something, так что foo[x:y] расширяется до

foo.__getitem__(slice(x, y, None))

и foo[x:y] = bar расширяется до

foo.__setitem__(slice(x, y, None), bar)

Теперь __getitem__ с срезом возвращает новый список, который является копией указанного диапазона, поэтому его изменение не влияет на исходный массив. И назначение работ в силу __setitem__ является другим методом, который может просто сделать что-то еще.

Однако обработка специального назначения относится только к самой внешней операции. Составляющие являются нормальными выражениями. Поэтому, когда вы пишете

foo[:][1] = 'two'

он расширяется до

foo.__getitem__(slice(None, None, None)).__setitem__(1, 'two')

часть foo.__getitem__(slice(None, None, None)) создает копию, и эта копия изменяется __setitem__. Но не исходный массив.

Ответ 2

foo[:] является копией foo. Вы мутировали копию.

Ответ 3

Главное отметить здесь, что foo[:] вернет себе копию, а затем индексирование [1] будет применено к скопированному списку, который был возвращен

# indexing is applied on copied list
(foo[:])[1] = 'two'
    ^
copied list

Вы можете просмотреть это, если сохранить ссылку на скопированный список. Таким образом, операцию foo[:][1] = 'two' можно переписать как:

foo = [1, 2, 3, 4]

# the following is similar to foo[:][1] = 'two'

copy_foo = foo[:]  
copy_foo[1] = 'two'

Теперь copy_foo был изменен:

print(copy_foo)
# [1, 'two', 3, 4]

Но, foo остается неизменным:

print(foo)
# [1, 2, 3, 4]

В вашем случае вы не указали промежуточный результат при копировании списка foo с помощью foo[:], то есть вы не сохранили ссылку на него. После того как присвоение 'two' выполняется с помощью foo[:][1] = 'two', промежуточный скопированный список перестает существовать.

Ответ 4

Использование

foo[1]  = 'two'

и

foo[2:] = ['three', 'four']

и он работает.

Ответ, который находится в комментарии выше (потому что вы используете копию)