views:

84

answers:

3

This is a simple question about how Python handles data and variables. I've done a lot of experimenting and have Python mostly figured out, except this keeps tripping me up:

[edit: I separated and rearranged the examples for clarity]

Example 1:

>>> a = [[1], 2]
>>> a[0:1]
[[1]]
>>> a[0:1] = [[5]]
>>> a
[[5], 2] # The assignment worked.

Example 2:

>>> a = [[1], 2]
>>> a[0:1][0]
[1]
>>> a[0:1][0] = [5]
>>> a
[[1], 2] # No change?

Example 3:

>>> a = [[1], 2]
>>> a[0:1][0][0]
1
>>> a[0:1][0][0] = 5
>>> a
[[5], 2] # Why now?

Can anybody explain to me what's going on here?

So far the answers seem to claim that a[0:1] returns a new list containing a reference to the first element of a. But I don't see how that explains Example 1.

+6  A: 

a[0:1] is returning a new array which contains a reference to the array [1], thus you end up modifying the inner array via a reference call.

The reason the first case doesn't modify the [1] array is that you're assigning the copied outer array a new inner array value.

Bottom line - a[0:1] returns a copy of the data, but the inner data is not copied.

koblas
@dln385: The last sentence of this post answers your question. The inner data (in your case, the number) is not copied, so modifying it changes the original.
musicfreak
The last sentence explains why `a[0:1][0][0] = 5` works. But `a[0:1] = [[5]]` isn't modifying the inner data, and so _shouldn't_ work. I think it must be handled as a special case, as [pyfunc](http://stackoverflow.com/questions/4055515/assigning-a-value-to-an-element-of-a-slice-in-python/4055556#4055556) seems to imply.
dln385
Yes, when the assignment statetement (i.e., the "=" signal) comes following a slice reference it is a completly different thing than when other operations are done. To be clear: `>>> a[0:1] = [[5]]` is equivalent to `a.__setitem__(slice(0,1,None), [5])` --- while `a[0:1][0] = 5` is the same as: `a.__getitem__(slice(0,1,None)).__setitem__(0, 5)`
jsbueno
+2  A: 

My understanding is slicing returns a new object. That is it's return value is a new list.

Hence you can not use an assignment operator to changes the values of the original list

>>> a = [[1], 2, 3]
>>> k = a[0:2]
>>> id(a)
4299352904
>>> id(k)
4299353552
>>> 

>>> id(a)
4299352904
>>> id(a[0:2])
4299352832

some more plays along the lines

>>> k = 5
>>> 
>>> id(k)
4298182344
>>> a[0] = [1,2]
>>> a
[[1, 2], 2, 3]
>>> id(a)
4299352904
>>> 

[Edit: on second part of question]

>>> a[0:1] = [[5]]

The following notation is also called commonly as slice assignment The behavior for builtin lists is atomic (delete + insert) happens in one go. My understanding is that this is not allowed for custom sequence.

pyfunc
This is correct. Slicing returns a new object. The same is true for assigning dictionary keys to a new variable (e.g. `foo = mydict[bar]`). Changes to the assigned slice do not modify the original reference because they are a copy.
jathanism
+1  A: 

There are three distinct operations with indices, all are translated to method calls:

  • a[i] = b => a.__setitem__(i, b)
  • del a[i] => a.__delitem__(i)
  • a[i] used as an expression => a.__getitem__(i)

Here a, b and i are expressions, and i can contain slice objects created using the colon shorthand syntax. E.g.:

>>> class C(object):
...     def __setitem__(self, *a):
...             print a
... 
>>> C()[1] = 0
(1, 0)
>>> C()['foo'] = 0
('foo', 0)
>>> C()['foo':'bar'] = 0
(slice('foo', 'bar', None), 0)
>>> C()['foo':'bar',5] = 0
((slice('foo', 'bar', None), 5), 0)

So what's happening in your third example is this:

a[0:1][0][0] = 5

becomes

a.__getitem__(slice(0,1)).__getitem__(0).__setitem__(0, 5)

The first __getitem__ returns a copy of part of the list, but the second __getitem__ returns the actual list inside that, which is then modified using __setitem__.

Your second example on the other hand becomes

a.__getitem__(slice(0,1)).__setitem__(0, 5)

So __setitem__ is being called on the sliced copy, leaving the original list intact.

adw