2

I was doing a simple exercise to understand the mechanics of List assignment. If I assign a list L1 to L2 and then append an element to L1, both L1 and L2 are changed. However if I assign a list L3 to a subslice of L2 and then append an element to L3, the changes in L3 are disconnected from L2. How is this accomplished? L3 actually points to a different location now which contains a subslice of the list while L1 and L2 point to the same location. Is that right?

>>> L1 = [] >>> L2 =[1,2,3] >>> L1 = L2 >>> L1.append(4) >>> L1 [1, 2, 3, 4] >>> L2 [1, 2, 3, 4] >>> L3 =L2[:2] >>> L3 [1, 2] >>> L3.append(5) >>> L3 [1, 2, 5] >>> L2 [1, 2, 3, 4] 
6
  • Creating a slice from a list creates a shallow copy...CommentedJul 21, 2013 at 23:28
  • @John why do you say shallow copy en.wikipedia.org/wiki/Object_copy#Deep_copy To me L4 = L2[:] seems like a deep copy because L4 now points to a new memory location that contains L2[:]
    – vkaul11
    CommentedJul 21, 2013 at 23:43
  • 1
    A deep copy would create deep copies of the elements of L2. You're thinking of the shallow copy as deep because the thing you're thinking of as a shallow copy - direct assignment - is not actually a copy at all. This is a fundamental difference between languages like C++, where variables are places to put things and assignment creates copies, and languages like Python, where variables are names for things, and assignment just moves nametags around.CommentedJul 21, 2013 at 23:47
  • user2357112 but the point is that when I append an element to L4 after L4=L2[:], how are the L2 and L4 disconnected? Is not because L4 and L2 point to a different memory location now though the list elements have the same values?
    – vkaul11
    CommentedJul 22, 2013 at 0:07
  • I guess it is because list is a compound object that contains other objects or else the distinction between shallow and deep copy does not exist in python docs.python.org/2/library/copy.html
    – vkaul11
    CommentedJul 22, 2013 at 0:12

3 Answers 3

3

The answer is simple: A an assignment assigns a reference. That’s why changes to L1 are visible in L2 – they’re the same object.

A slice, however, creates a (shallow) copy of the range. Hence changes to L3 are disconnected from L2.

In fact, in order to create a copy of a sequence, since you cannot use direct assignment, you can use a slice instead:

>>> L4 = L2[:] >>> L4.append(5) >>> L2 [1, 2, 3, 4] 

… however, that’s more commonly done via the constructor, i.e. L4 = list(L2).

3
  • So basically L4 = L2[:] is like a shallow copy Konrap in C++? To accomplish the same for a general class what should I do?
    – vkaul11
    CommentedJul 21, 2013 at 23:35
  • To me L4 = L2[:] seems like a deep copy because L4 now points to a new memory location that contains L2[:]
    – vkaul11
    CommentedJul 21, 2013 at 23:42
  • @vkaul11 That’s a shallow copy. “Deep copy” would mean that all the elements inside the list also get copied but that’s not true – try putting a modifiable element in a list, copy the list and modify the object. It will be modified in all instances of the list because the lists merely store a reference to the object, not the object itself.CommentedJul 22, 2013 at 8:37
1

(I wasted too much time in examples and @Konrad Rudolph beat me to the answer, I'm basically saying the same thing as him)

Python is doing the equivalent of C pointer assignment under the hood, L1 held the address for a list in memory, and L2 just copied the same address.

You can check in code, simply using the is operator.

>>> L1 is L2 True 

When using slices though, a new list must be created, since it will differ from the original one, but python, being the sneaky bastard that it is, instead of copying everything, it just copies the references to the objects inside.

Here's a more elaborate example:

>>> L1 = [1, [2, 3], 4] >>> L2 = L1 >>> L2 [1, [2, 3], 4] >>> L1[1] is L2[1] True 

Ok, so L1 holds a list with a list inside, you could replace this with a dictionary, string or any other python object. Notice that the is operator returned True for the list inside the list.

Then, if you do the slicing trick:

>>> L3 = L1[:2] >>> L3 [1, [2, 3]] >>> L3 is L1 False >>> L3[1] is L2[1] True 

The outside list has changed, but the objects within remain the same, this is known as shallow copying. In fact, the same rules keep applying, to the point if that we add a new item to the list within the list:

>>> L3[1].append(9) >>> L3 [1, [2, 3, 9]] >>> L1 [1, [2, 3, 9], 4] 

(notice the added 9) The list inside of both L1 and L3 are changed.

By contrast, you can do deepcopy when shallow copying is not desired:

>>> from copy import deepcopy >>> L3 = deepcopy(L1) >>> L3 [1, [2, 3, 9], 4] >>> L3 is L1 False 
    0

    To add to the answer above, the nature of the copy becomes important when you have a list of mutable objects, instead of integers or strings. The elements of the lists still point to the same objects even when you slice.

    >>> a = { 'a': 0 } >>> b = { 'b' : 0 } >>> c = { 'c' : 0 } >>> l = [ a, b, c ] >>> m = l[ : 2] 

    m and l contain references to the same things.

    >>> m [{'a': 0}, {'b': 0}] >>> m[0]['a'] = 1 >>> m [{'a': 1}, {'b': 0}] >>> l [{'a': 1}, {'b': 0}, {'c': 0}] 

    However, m and l are different things.

    >>> m[0] = {'d': 1} >>> m [{'d': 1}, {'b': 0}] >>> l [{'a': 1}, {'b': 0}, {'c': 0}] 

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.