Как присваивание работает с фрагментом списка Python?

102

Документ Python говорит, что нарезка списка возвращает новый список.
Теперь, если возвращается «новый» список, у меня возникают следующие вопросы, связанные с «Присвоением срезов»

a = [1, 2, 3]
a[0:2] = [4, 5]
print a

Теперь вывод будет:

[4, 5, 3] 
  1. Как может то, что возвращает что-то, появиться в левой части выражения?
  2. Да, я прочитал документацию, и там говорится, что это возможно, поскольку теперь, когда нарезка списка возвращает «новый» список, почему изменяется исходный список? Я не могу понять механику этого.
Картик Ананд
источник
@Mark Longair, извините, я думал, что должен быть отформатирован только код, а не вывод
Картик Ананд
7
Вы понимаете, что a[0] = 4бы сделали?
Джош Ли
1
@KartikAnand Назначение среза - это особый сценарий, при котором новый список не создается. Не имеет смысла создавать объект без привязки имени в левой части =, поэтому вместо того, чтобы отбрасывать это как недопустимый синтаксис, python превращает его во что-то более похожее на то, что вы могли ожидать. Поскольку у python нет ссылок, не получится изменить исходный список в результате среза. Вы получаете копию. Если вы предоставили дополнительную информацию о своем приложении, мы могли бы лучше помочь вам делать что-то «питоническим» способом. :)
Кейси Кубалл
1
@Darthfett Я не работаю ни над одним приложением прямо сейчас, скорее я учу себя питону, прежде чем начну пачкать руки :)
Картик Ананд

Ответы:

116

Вы путаете две разные операции, использующие очень похожий синтаксис:

1) нарезка:

b = a[0:2]

Это делает копию фрагмента aи назначает его b.

2) назначение срезов:

a[0:2] = b

Это заменяет фрагмент aсодержимымb .

Хотя синтаксис похож (я предполагаю, что это дизайн!), Это две разные операции.

NPE
источник
4
Вот в чем я сомневаюсь, во втором случае, почему часть а не новый список ??
Картик Ананд
12
@KartikAnand Потому что это не так. Это не то, что указывает язык.
Марцин
Чтобы быть ясным, «берет кусок» на самом деле означает «сделать копию кусочка», откуда и происходит часть путаницы.
Марк Рэнсом
2
@KartikAnand: В принципе, да. Интерпретатор знает, что есть что, и обрабатывает их соответствующим образом.
NPE
1
@Dubslow: это можно сделать с помощью модуля itertools . В вашем случае использовать функцию Ислицу , с start=1, stop=None. Это позволит избежать копий и использовать ленивую оценку (в вашем случае ленивый доступ к исходному списку).
Spiros
68

Когда вы указываете aслева от =оператора, вы используете обычное присвоение Python , которое изменяет имя aв текущем контексте, чтобы указать на новое значение. Это не меняет предыдущее значение, на котороеa указывалось.

Указывая a[0:2]слева от =оператора, вы сообщаете Python, что хотите использовать Slice Assignment . Назначение фрагментов - это специальный синтаксис для списков, в котором вы можете вставлять, удалять или заменять содержимое из списка:

Вставка :

>>> a = [1, 2, 3]
>>> a[0:0] = [-3, -2, -1, 0]
>>> a
[-3, -2, -1, 0, 1, 2, 3]

Удаление :

>>> a
[-3, -2, -1, 0, 1, 2, 3]
>>> a[2:4] = []
>>> a
[-3, -2, 1, 2, 3]

Замена :

>>> a
[-3, -2, 1, 2, 3]
>>> a[:] = [1, 2, 3]
>>> a
[1, 2, 3]

Примечание:

Длина среза может отличаться от длины назначенной последовательности, таким образом изменяя длину целевой последовательности, если целевая последовательность позволяет это. - источник

Функция Slice Assignment аналогична функции Tuple Unpacking . Например, a[0:1] = [4, 5]эквивалентно:

# Tuple Unpacking
a[0], a[1] = [4, 5]

С помощью распаковки кортежей вы можете изменять непоследовательные списки:

>>> a
[4, 5, 3]
>>> a[-1], a[0] = [7, 3]
>>> a
[3, 5, 7]

Однако распаковка кортежа ограничивается заменой, так как вы не можете вставлять или удалять элементы.

До и после всех этих операций a- один и тот же точный список. Python просто предоставляет хороший синтаксический сахар для изменения списка на месте.

Кейси Кубалл
источник
6
Похожи, но не идентичны, поскольку слева и справа может быть неравное количество элементов.
Марк Рэнсом
@MarkRansom Это отличный момент, я добавил дополнительную информацию, чтобы сделать это очевидным.
Кейси Кубалл
2
Является ли a[:] = some_listэквивалент a = some_list[:]или a = some_list?
jadkik94
2
@ jadkik94 Ни то, ни другое. a[:] = some_listустанавливает, что каждый элемент aиз some_list. Выполнение любого из упомянутых вами действий изменит то, что aесть. Например: a = [1, 2, 3] b = a a[:] = [4, 5, 6] a is b. Последняя строка будет иметь значение False, если она изменяет aзначение, а не изменяет его.
Кейси Кубалл
@Darthfett Интересно, я нашел другое :) Спасибо.
jadkik94
25

Раньше я сталкивался с тем же вопросом, и он связан со спецификацией языка. Согласно заданиям-заявлениям ,

  1. Если левая часть назначения - это подписка, Python вызовет __setitem__этот объект. a[i] = xэквивалентно a.__setitem__(i, x).

  2. Если левая сторона присваивания - срез, Python также вызовет __setitem__, но с другими аргументами: a[1:4]=[1,2,3]эквивалентно a.__setitem__(slice(1,4,None), [1,2,3])

Вот почему срез списка слева от '=' ведет себя иначе.

Стэн
источник
4

Нарезая левую часть операции присваивания, вы указываете, какие элементы назначать.

Fraxel
источник