3

I have encountered the following unexpected behavior when changing values of a slice of a list (by index):

# Expected behavior:
>>> a = [1,2,3,4]
>>> a
[1, 2, 3, 4]
>>> a[2] = 5
>>> a
[1, 2, 5, 4]
>>> a[2:]
[5, 4]
>>> a[2:] = ['y', 2]
>>> a
[1, 2, 'y', 2]
>>> a[1:]
[2, 'y', 2]
>>> a[1:][0]
2
# Unexpected behavior:
>>> a[1:][0] = 'hello'
>>> a
[1, 2, 'y', 2] # I would have expected [1, 'hello', 'y', 2] here

As you can see, you can overwrite the original list by assigning a list to a slice, like in a[2:] = ['y', 2]. However, if you try to overwrite elements of a slice by index (or by a slice-of-slice construction like >>> a[1:][:1] = [2]), there is no error, the original list does not get updated however.

Is there a proper way of doing this? Why does Python (3.7.3) behave this way?

  • Assigning to a slice is its own operation. The `a[1:]` in `a[1:][0]` and `a[1:] = 'hello'` isn’t an equivalent expression between the two. `a[]` as an expression evaluates to a list, and `a[] = …` copies an iterable into part of `a`. – Ry- Aug 23 '19 at 08:40
  • https://stackoverflow.com/questions/3485475/can-i-create-a-view-on-a-python-list – Ry- Aug 23 '19 at 08:41

2 Answers2

3

When you slice a list, you actually create a copy of the list (the part you sliced) so they can't affect each other:

x = [1, 2, 3, 4]
y = x[1:]  # y = [2, 3, 4]

y[0] = "hi"  # y = ["hi", 3, 4]
# y is a copy of x, so any changes made to y aren't reflected in x

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

The index assignment operator can work with one index (i.e x[i] = something) or with a slice object (start:stop:step, i.e x[1:5] = [1, 2, 3, 4]). However you tried to change the elements of a slice, which results in no change:

x[1:][0] = "hi"  # x is unchanged
# x[1:] is a copy of x, so changes to it (like setting the first element to "hi")
# does nothing useful

Instead, you can calculate the index or the range yourself:

# instead of:
x[start:][1] = "hi"
# use:
x[start + 1] = "hi"
Shai Avr
  • 942
  • 8
  • 19
2

The reason this happens is because the slice a[1:] is an entirely separate list from the original a. While this is only a shallow copy, since the objects you are storing in the list are immutable values, the shallow copy copies them by value rather than by reference. Therefore, when you update the object in the sliced list, the change is not reflected in the original list.

The reason why this is different behaviour from the example a[2:] = ['y', 2], where the original list actually is modified, is because that syntax refers to a special behaviour called slice assignment, which specifically does replace the contents of the original list with the list on the right-hand side of the assignment. This behaviour is not triggered if you further sub-index the slice on the left-hand side of the assignment.

Rob Streeting
  • 1,675
  • 3
  • 16
  • 27
  • 1
    But if `a[1:]` is a separate object, why does `a[2:] = ['y', 2]` work, i.e. why is this change reflected in the original list `a`? I would have thought `a[2:] = ['y', 2]` to be equivalent to `a[2:][0], a[2:][1] = 'y', 2`, which it isn't. – Wojciech Morawiec Aug 23 '19 at 08:51
  • 1
    Sorry I didn't mention that as part of my answer, I will update it to include why that is. – Rob Streeting Aug 23 '19 at 08:54