1

I tried to make a copy of a 2 dimensional array using the splicing operator. Intuitivelly, it feels like if I do this:

L = [[5, 6], [7, 8]] 
M = L[:][:] 

then M would be a cloned copy of every nested element in L. So I could change items in M[0] without changing L:

M[1] = [3, 4] 
M[0][1] = 2 

But when I do this:

print(L)
#returns [[5,2],[7,8]]

What is actually going on in Python's memory when I performed the [:][:] operation?

Zoe
  • 27,060
  • 21
  • 118
  • 148

2 Answers2

3

Just makes a copy of the list on the left side of the operator [:].

[:][:] does copy of copy. You can do M = L[:][:][:][:][:][:][:][:][:][:][:][:][:][:] and it works the same way :D

To make a copy of nested lists as well you should do it like M = [x[:] for x in L[:]]

Then M would be a cloned copy of every nested element in L. It is not true because L[:][:] is the same as (L[:])[:] it is a copy of the copy of the original list.

M[1] = [3, 4] here you modify the second item of the COPY.

But here M[0][1] = 2 you are modifying an item of the original nested array since [:] doesn't do any copy operations on nested lists and the copied original list item was not "simple".

Alexandr Shurigin
  • 3,921
  • 1
  • 13
  • 25
  • 1
    There's no point in slicing `L` in `M = [x[:] for x in L[:]]`. `M = [x[:] for x in L]` would make the same copy, but faster because it doesn't make an unnecessary shallow copy of `L` first. – user2357112 Dec 15 '19 at 05:31
  • of course not, just wanted to show some more examples for the source code block without touching it too much. Show "how it works" deeper – Alexandr Shurigin Dec 15 '19 at 05:32
1

Warning! In Python, when it's a list and doing x = y, changing values in y changes x.

It's not a copy by value but copy by reference here. E.g.

>>> x = [[1,2], [3,4]]
>>> y = x
>>> y[0][1] = 5 
>>> x
[[1, 5], [3, 4]]

But how you're copying using x = y[:] is right. And the chaining of [:] is redundant.

First lets understand how to really "copy by value" for this nested list. To do so, in general, you can use the copy module.

>>> import copy
>>> x = [[1,2], [3,4]]
>>> y = copy.deepcopy(x)
>>> y[0][1] = 5
>>> x
[[1, 2], [3, 4]]
>>> y
[[1, 5], [3, 4]]

From https://docs.python.org/3/faq/programming.html#how-do-i-copy-an-object-in-python , doing y = x[:] is the same as using copy.copy

Now going back to the [:] notation. If we go down the nested-ness and look at the types:

>>> x = [[1,2], [3,4]]
>>> type(x)
<class 'list'>
>>> type(x[0])
<class 'list'>
>>> type(x[:])
<class 'list'>
>>> type(x[:][0])
<class 'list'>
>>> type(x[:][0][0])
<class 'int'>
>>> type(x[:][0][0][0])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not subscriptable

We see that if we directly assess the values inside the inner list at some point the integer value is not a list and so we can't get an index from it.

What happens when we keep on doing x[:][:][:][:][:], we see that it checks out at it's allowing us to keep on accessing the list but are we really getting into the nested list values when we do [:]?

>>> type(x[:][:][:])
<class 'list'>
>>> type(x[:][:][:][:][:])
<class 'list'>
>>> type(x[:][:][:][:][:][:])
<class 'list'>

Here we see the types are not changing, and if we look at the actual value, if we start copying it:

>>> x = [[1,2], [3,4]]
>>> y = copy.deepcopy(x)
>>> y
[[1, 2], [3, 4]]
>>> y = copy.deepcopy(x[:])
>>> y
[[1, 2], [3, 4]]
>>> y = copy.deepcopy(x[:][:])
>>> y
[[1, 2], [3, 4]]
>>> y = copy.deepcopy(x[:][:][:])
>>> y
[[1, 2], [3, 4]]
>>> y = copy.deepcopy(x[:][:][:][:])
>>> y
[[1, 2], [3, 4]]

Essentially, you are accessing the full list at the first x[:] and then the continuous chaining of [:] isn't exactly accessing/traversing the nested loop, but just re-copying the copied list.

Think of z=x[:][:] as:

y = x[:] 
z = y[:]

It's a good point now to read the documentations/questions on slicing

To dig deeper, you'll have to go into the implementation. https://github.com/python/cpython/blob/master/Objects/sliceobject.c

alvas
  • 115,346
  • 109
  • 446
  • 738