15

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] = ['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] 

Why isn't foo updated after foo[:][1] = 'two'?

Update: Maybe I didn't explain my questions clearly. I know when slicing, a new list is created. My doubt is why a slicing assignment updates the list (e.g. foo[:1] = ['one']), but if there are two levels of slicing, it doesn't update the original list (e.g. foo[:][2:] = ['three', 'four']).

Will Vousden
  • 32,488
  • 9
  • 84
  • 95
Peng Li
  • 152
  • 7
  • 6
    Have you used `numpy` in the past, perhaps? Numpy arrays use slicing differently from Python lists. – Martijn Pieters Jan 14 '16 at 08:28
  • Please read https://en.wikipedia.org/wiki/Value_(computer_science)#lrvalue – Dima Tisnek Jan 14 '16 at 09:00
  • congratulations! you have discovered [how to clone or copy a list](http://stackoverflow.com/a/2612815/3904031)! next ask [how deep did the copy go?](http://stackoverflow.com/a/26562235/3904031) – uhoh Jan 14 '16 at 10:06

4 Answers4

20

foo[:] is a copy of foo. You mutated the copy.

TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
  • https://en.wikipedia.org/wiki/Value_(computer_science)#lrvalue is worth adding here, `foo[:]` may look the same, but it's an lvalue in `foo[:] = ...` and an rvalue in `foo[:] [2:] = ...` – Dima Tisnek Jan 14 '16 at 09:00
  • 2
    @qarma, it is incorrect to speak of lvalues and rvalues in Python. It does not have them. In python `[]=` is simply a different operator from `[]`. – Jan Hudec Jan 14 '16 at 12:47
  • Yes that's correct, please provide an explanation in the answer for the rest to see. – Dima Tisnek Jan 14 '16 at 13:25
  • @qarma: I think you're confusing two different people. Jan Hudec's answer already explains all of that. – ruakh Jan 14 '16 at 20:16
  • But that's not what you're doing with `foo[:][1] = 'two'`, so it isn't the case. – TigerhawkT3 Jan 14 '16 at 21:39
  • @TigerhawkT3 see I understand your answer exactly, but not everyone does. Shall I expand your answer? – Dima Tisnek Jan 15 '16 at 08:17
  • @qarma - If you have a substantial contribution to make, why not create your own answer? No need to put my name on your efforts/ideas. :) – TigerhawkT3 Jan 15 '16 at 08:25
17

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.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
13

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.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
3

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)

CodeMonkey
  • 4,067
  • 1
  • 31
  • 43