Slicing, list assignment and deep/shallow copy

Question:

From the problem ‘Removes any element from L1 that also occurs in L2’

  1. first case
def removeDups(L1, L2):
    L3 = L1[:] 
    for e1 in L3:
        print('L3' + str(L3))
        print('L1: ' + str(L1))
        if e1 in L2:
            L1.remove(e1)
L1 = [1,2,3,4]
L2 = [1,2,5,6]
removeDups(L1, L2)

Output:

L3[1, 2, 3, 4]
L1: [1, 2, 3, 4]
L3[1, 2, 3, 4]
L1: [2, 3, 4]
L3[1, 2, 3, 4]
L1: [3, 4]
L3[1, 2, 3, 4]
L1: [3, 4]
  1. second case
def removeDups(L1, L2):
    for e1 in L1[:]:
        print('L1[:]: ' + str(L1[:]))
        print('L1: ' + str(L1))
        if e1 in L2:
            L1.remove(e1)
L1 = [1,2,3,4]
L2 = [1,2,5,6]
removeDups(L1, L2)

Output:

L1[:]: [1, 2, 3, 4]
L1: [1, 2, 3, 4]
L1[:]: [2, 3, 4]
L1: [2, 3, 4]
L1[:]: [3, 4]
L1: [3, 4]
L1[:]: [3, 4]
L1: [3, 4]

Two things confuse me:

  1. why would the second case even work? I suspect, slicing makes a shallow copy of L1, and the hidden counter in for loop would ignore the element 2 as L1[:]’s first element is removed too. Thus 2 is in index 0 after the first element is removed. In general, why is this element still accessed by the for loop? That’s why I suspect it should print as below:
L1[:]: [1, 2, 3, 4]
L1: [1, 2, 3, 4]
L1[:]: [2, 3, 4]
L1: [2, 3, 4]
L1[:]: [2, 3, 4]
L1: [2, 3, 4]
  1. Why would L3 in the first case remain in the original list after L1 removed some elements?
    What is the mechanism for this line of codeL3 = L1[:]. Is it something like a deep copy? I thought it just gets a shallow copy of L1 eventually.
Asked By: HugoWang

||

Answers:

The first case and the second case aren’t the same.

In the first case, as you suspect, you create a shallow copy of L1.
And in the second case you check the mutated value of L. The slicing doesn’t change this matter.

def removeDups(L1, L2):
    for e1 in L1[:]:  # Create a shallow copy of L1
        print('L1[:]: ' + str(L1[:]))  # Check current state of L1 new slice
        print('L1: ' + str(L1))  # Check current state of L1
        if e1 in L2:
            L1.remove(e1)

So even though you check a copy of L1 it changes with each loop, as you always check the mutated version of L1. L3 on the other hand doesn’t change, as it is a new object. So the call to L3 will always give the original list.

The for loop still works, as the L1[:] in for e1 in L1[:] is only evaluated at the beginning of the loop. So you iterate over a shallow copy of L1.

Answered By: Wolric

these lines are equivalent and create a shallow copy.

L3 = L1[:]
L3 = list(L1)
L3 = L1.copy()
L3 = [x for x in L1]

shallow copying L1 to L3 is equivalent to passing L1[:] to the loop,

something that confuses new people is the difference between a shallow copy, a deep copy, and an assignment.

L3 = L1  # assignment, modifications to L1 affects L3 and vice versa
L3 = L1[:]  # shallow copy, allocated a new block of memory that gets assigned to L3
L3 = copy.deepcopy(L1)  # deep copy, makes a deep copy of each element of L1 as well as L1 itself.

for immutable types such as numbers or strings you won’t find difference between the last 2, but if L1 was a list of lists, then those lists will be shared in the case of a shallow copy, but won’t be shared in the case of a deep copy, and any modification to L1 elements won’t affect L3 elements.

i am also obligated to post this link to Facts and myths about Python names and values

Answered By: Ahmed AEK

Slicing create a new list, and as you said is a shallow copy of the original list by only keeping the references of the objects it contains.

Now, for your first question:

Why wouldn’t it ? Sementically, it is exactly the same, with only L3 being created anynonymously. In the iterator L1[:] is only executed once, create the sliced list, and then your code iterate over it. Reference to L1 can’t affect this anonymously created list, because it has not the same reference !

For your second question: it almost the same as your previous question, and you’re prints allows to understand:

L1 = [1, 2, 3, 4]
L3 = L1[:]

So L3 is now:

[1, 2, 3, 4]

but if we do :

>>> L3 is L1
False

The list is … a copy !

Answered By: Sami Tahri
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.