E0_copy is not a deep copy. You don't make a deep copy using list(). (Both list(...) and testList[:] are shallow copies, as well as testList.copy().)
You use copy.deepcopy(...) for deep copying a list.
copy.deepcopy(x[, memo])Return a deep copy of x.
See the following snippet -
>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = list(a)
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0][1] = 10
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b # b changes too -> Not a deepcopy.
[[1, 10, 3], [4, 5, 6]]
Now see the deepcopy operation
>>> import copy
>>> b = copy.deepcopy(a)
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b
[[1, 10, 3], [4, 5, 6]]
>>> a[0][1] = 9
>>> a
[[1, 9, 3], [4, 5, 6]]
>>> b # b doesn't change -> Deep Copy
[[1, 10, 3], [4, 5, 6]]
To explain, list(...) does not recursively make copies of the inner objects. It only makes a copy of the outermost list, while still referencing the same inner lists, hence, when you mutate the inner lists, the change is reflected in both the original list and the shallow copy. You can see that shallow copying references the inner lists by checking that id(a[0]) == id(b[0]) where b = list(a).
E0_copy is not a deep copy. You don't make a deep copy using list(). (Both list(...) and testList[:] are shallow copies, as well as testList.copy().)
You use copy.deepcopy(...) for deep copying a list.
copy.deepcopy(x[, memo])Return a deep copy of x.
See the following snippet -
>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = list(a)
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0][1] = 10
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b # b changes too -> Not a deepcopy.
[[1, 10, 3], [4, 5, 6]]
Now see the deepcopy operation
>>> import copy
>>> b = copy.deepcopy(a)
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b
[[1, 10, 3], [4, 5, 6]]
>>> a[0][1] = 9
>>> a
[[1, 9, 3], [4, 5, 6]]
>>> b # b doesn't change -> Deep Copy
[[1, 10, 3], [4, 5, 6]]
To explain, list(...) does not recursively make copies of the inner objects. It only makes a copy of the outermost list, while still referencing the same inner lists, hence, when you mutate the inner lists, the change is reflected in both the original list and the shallow copy. You can see that shallow copying references the inner lists by checking that id(a[0]) == id(b[0]) where b = list(a).
In Python, there is a module called copy with two useful functions:
import copy
copy.copy()
copy.deepcopy()
copy() is a shallow copy function. If the given argument is a compound data structure, for instance a list, then Python will create another object of the same type (in this case, a new list) but for everything inside the old list, only their reference is copied. Think of it like:
newList = [elem for elem in oldlist]
Intuitively, we could assume that deepcopy() would follow the same paradigm, and the only difference is that for each elem we will recursively call deepcopy, (just like mbguy's answer)
but this is wrong!
deepcopy() actually preserves the graphical structure of the original compound data:
a = [1,2]
b = [a,a] # there's only 1 object a
c = deepcopy(b)
# check the result
c[0] is a # False, a new object a_1 is created
c[0] is c[1] # True, c is [a_1, a_1] not [a_1, a_2]
This is the tricky part: during the process of deepcopy(), a hashtable (dictionary in Python) is used to map each old object ref onto each new object ref, which prevents unnecessary duplicates and thus preserves the structure of the copied compound data.
Official docs
Do I need to be using deepcopy with numpy arrays in this situation?
python - Deep copy of a np.array of np.array - Stack Overflow
Python copy.copy() appears to make a deep copy
Why choose to copy a list with slices instead of copy(), deepcopy(), or list()??
Videos
I've done a ton of reading about this and I sort of know the difference between the two, but I still don't know when I need to use copy.copy() vs copy.deepcopy(). It seems like copy.copy() makes a new object of the "container" (for instance, if it is a list then you get a new list object) but then populates this container with the references to the original element objects (so each element in the two different lists point to the same spot in memory).
Essentially, my question is that I have a bunch of numpy arrays and I don't think I'm using copy correctly (or if I need ot be using it at all). It seems like if I am only doing assignments or using these values to set other values (eg output = self.weights*input or whatever) then I don't need to make copies (its fine that the references point to the same spot in memory since I just need read not write access), but if I am doing things like increments / decrements or setting the new value based on the previous value, then I would be changing the values in memory and these are shared by all my objects? Some code:
For instance, let's say I have some Parent class and it has its children Child objects and the parent sends its models to the children and the children update the models based on their own data. Specifically, each child should get (a copy?) the parent's weight matrix (numpy array) to use as initialization, and then start updating its own matrix (not shared with any other children or the parent).
parent = Parent()
child1 = Child()
child2 = Child()
...
childN = Child()
for child in children_list:
child.weights = parent.weights # copy? deepcopy?
for child in children_list:
for _ in num_grad_steps:
child.weights -= child.learning_rate * child.gradient(child.weights, child.input_data)
# Send the updated weights back to the parent
parent.new_weights_list.append(child.weights)
In this case, child.weights is being updated by each child (note I don't want a single matrix that is updated by all children, I want one matrix as initialization, then every child runs with it so I end up with N unique final matrices), and if it is either just the same object (basic assignment: child.weights = self.weights) or just a shallow copy (child.weights = copy.copy(parent.weights)) then FOR ALL THE CHILDREN the weights are shared instead of each child getting the same initial matrix and privately updating its own copy, right? If I instead switch this to child.weights = copy.deepcopy(parent.weights) then I think this fixes it, but since my weights matrix is pretty large this just takes an extremely long time to run (and it seems like I'm missing something, like it shouldn't be this hard. I feel like code I see online in similar applications doesn't do this, but many don't have the weights distributed across so many objects at once I guess?). Do I need to be using deepcopy or is there something I'm missing? Greatly appreciate any help!
import numpy as np
import copy
pairs = [(2, 3), (3, 4), (4, 5)]
array_of_arrays = np.array([np.arange(a*b).reshape(a,b) for (a, b) in pairs])
a = copy.deepcopy(array_of_arrays)
Feel free to read up more about this here.
Oh, here is simplest test case:
a[0][0,0]
print a[0][0,0], array_of_arrays[0][0,0]
In [276]: array_of_arrays
Out[276]:
array([array([[0, 1, 2],
[3, 4, 5]]),
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]]),
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])], dtype=object)
array_of_arrays is dtype=object; that means each element of the array is a pointer to an object else where in memory. In this case those elements are arrays of different sizes.
a = array_of_arrays[:]
a is a new array, but a view of array_of_arrays; that is, it has the same data buffer (which in this case is list of pointers).
b = array_of_arrays[:][:]
this is just a view of a view. The second [:] acts on the result of the first.
c = np.array(array_of_arrays, copy=True)
This is the same as array_of_arrays.copy(). c has a new data buffer, a copy of the originals
If I replace an element of c, it will not affect array_of_arrays:
c[0] = np.arange(3)
But if I modify an element of c, it will modify the same element in array_of_arrays - because they both point to the same array.
The same sort of thing applies to nested lists of lists. What array adds is the view case.
d = np.array([np.array(x, copy=True) for x in array_of_arrays])
In this case you are making copies of the individual elements. As others noted there is a deepcopy function. It was designed for things like lists of lists, but works on arrays as well. It is basically doing what you do with d; recursively working down the nesting tree.
In general, an object array is like list nesting. A few operations cross the object boundary, e.g.
array_of_arrays+1
but even this effectively is
np.array([x+1 for x in array_of_arrays])
One thing that a object array adds, compared to a list, is operations like reshape. array_of_arrays.reshape(3,1) makes it 2d; if it had 4 elements you could do array_of_arrays.reshape(2,2). Some times that's handy; other times it's a pain (it's harder to iterate).