I thought I understood Python slicing operations, but when I tried to update a sliced list, I got confused:
>>> foo = [1, 2, 3, 4]
>>> foo[:1]
foo[:]
is a copy of foo
. You mutated the copy.
This is because python does not have l-values that could be assigned. Instead, some expressions have an assignment form, which is different.
A foo[something]
is a syntactic sugar for:
foo.__getitem__(something)
but a foo[something] = bar
is a syntactic sugar for rather different:
foo.__setitem__(something, bar)
Where a slice is just a special case of something
, so that foo[x:y]
expands to
foo.__getitem__(slice(x, y, None))
and foo[x:y] = bar
expands to
foo.__setitem__(slice(x, y, None), bar)
Now a __getitem__
with slice returns a new list that is a copy of the specified range, so modifying it does not affect the original array. And assigning works by the virtue of __setitem__
being a different method, that can simply do something else.
However the special assignment treatment applies only to the outermost operation. The constituents are normal expressions. So when you write
foo[:][1] = 'two'
it gets expanded to
foo.__getitem__(slice(None, None, None)).__setitem__(1, 'two')
the foo.__getitem__(slice(None, None, None))
part creates a copy and that copy is modified by the __setitem__
. But not the original array.
Use
foo[1] = 'two'
and
foo[2:] = ['three', 'four']
and it works.
The answer why is in the comment above (because you're using a copy)
The main thing to notice here is that foo[:]
will return a copy of itself and then the indexing [1]
will be applied on the copied list that was returned
# indexing is applied on copied list
(foo[:])[1] = 'two'
^
copied list
You can view this if you retain a reference to the copied list. So, the foo[:][1] = 'two'
operation can be re-written as:
foo = [1, 2, 3, 4]
# the following is similar to foo[:][1] = 'two'
copy_foo = foo[:]
copy_foo[1] = 'two'
Now, copy_foo
has been altered:
print(copy_foo)
# [1, 'two', 3, 4]
But, foo
remains the same:
print(foo)
# [1, 2, 3, 4]
In your case, you didn't name the intermediate result from copying the foo
list with foo[:]
, that is, you didn't keep a reference to it. After the assignment to 'two'
is perfomed with foo[:][1] = 'two'
, the intermediate copied list ceases to exist.