The rules of list references? - python

I got really confused on the list reference in python. Please help me understand. a simple case as below:
arr1 = []
arr2 = [1, 2]
arr1.append(arr2)
#arr2[0] = 5
arr2 = [6]
print(arr1)
So after append arr2 to arr1 without deep copy, to my understanding any change on arr2 would be reflected in arr1. However, only changing components like arr2[0] = 5 updates the arr1, while arr2 = [6] won't. Any reason why?

to my understanding any change on arr2 would be reflected in arr1
This is true for mutation, but assigning a new object to a label does not mutate the object, you are going to create a new list object [6] in memory and assign it to that label. arr2 now points to this new object(with different id()) not the one stored in arr1.
List objects are mutable so you can mutate them with lets say .append() method. In this case, any change to arr2 using .append() will reflect the list stored in arr1
arr1 = []
arr2 = [1, 2]
arr1.append(arr2)
arr2.append(6)
print(arr1)
Anytime you want to check if they are the same objects or not, simply print ids before and after. In case of mutation:
print(id(arr1[0])) # 2157378023744
print(id(arr2)) # 2157378023744

When you execute arr2 = [6], you create a new list object that is now referenced by arr2. The reference in the list still points to the initial list, so you cannot use the label arr2 anymore to change the contents of the list referenced in arr1.

Related

Id should be similar but it isnt can someone explain me why

the id of the object before and after should be same but its not happening. can someone explain me why a new object is being made.
L = [1, 2, 3]
print(id(L))
L = L + [4]
print(id(L))
both id's are that are being printed is different shouldn't it be the same its a mutable object. but when i use the append method of list to add 4 then the id is same
While lists are mutable, that doesn't mean that all operations involving them mutate the list in place. In your example, you're doing L + [4] to concatenate two lists. The list.__add__ method that gets invoked to implement that creates a new list, rather than modifying L. You're binding the old name L to the new list, so the value you get from id(L) changes.
If you want to mutate L while adding a value onto the end, there are several ways you can do it. L.append(4) is the obvious pick if you have just a single item to add. L.extend([4]) or the nearly synonymous L += [4] can work if the second list has more items in it than one.
Note that sometimes creating a new list will be what you want to do! If want to keep an unmodified reference to the old list, it may be desirable to create a new list with most of its contents at the same time you add new values. While you could copy the list then use one of the in place methods I mentioned above, you can also just use + to copy and add values to the list at the same time (just bind the result to a new name):
L = [1, 2, 3]
M = L + [4] # this is more convenient than M = list(L); M.append(4)
print(L) # unchanged, still [1, 2, 3]
print(M) # new list [1, 2, 3, 4]
its a mutable object
yes, you can change the value without creating a new object. But with the +, you are creating a new object.
To mute a mutable value, use methods (such as append) or set items (a[0] = ...). As soon as you have L=, the object formerly referenced by L is lost (if it doesn't have any other references) and L gets a new value.
This makes sense because, in fact, with L = L+[0], you are saying "calculate the value of L+[0] and assign it to L" not "add [0] to L".

Changing list elements in shallow copy

I have one question about list shallow copy.
In both examples, I modified one element of the list, but in example 1, list b changed, while in example 2, list d is not changed. I am confused since in both examples, I modified an element of the list.
What's the difference?
Example 1:
a=[1,2,[3,5],4]
b=list(a)
a[1]=0
print(a) # [1, 0, [3, 5], 4]
print(b) # [1, 2, [3, 5], 4]
Example 2:
c=[1,2,[3,5],4]
d=list(c)
c[2][0]=0
print(c) # [1, 2, [0, 5], 4]
print(d) # [1, 2, [0, 5], 4]
A shallow copy means that you get a new list but the elements are the same. So both lists have the same first element, second element, etc.
If you add, remove, or replace a value from the shallow copied list that change is not reflected in the original (and vise-versa) because the shallow copy created a new list. However if you change an element in either that change is visible in both because both lists reference the same item. So the inner list is actually shared between both the new list and the old list and if you change it, that change is visible in both.
Note that you actually didn't change an element in either example, you replace an element of the list in the first example and in the second example, you replace an element of an element of your list.
I'm currently using graphviz a lot so let me add some images to illustrate this:
The shallow copy means you get a new list but the objects stored in the list are the same:
If you replace an element in any of these the corresponding element will just reference a new item (your first example). See how one list references the two and the other the zero:
While a change to an referenced item will change that item and every object that references that item will see the change:
[1. = copies the reference of object, hence any changes in either list, reflects in another
b=list(a) or b=a.copy() -> do the same work.
That is it copies the reference of only the individual objects i.e like b[0]=a[0] and b2=a2 and so on. With int, string etc, it's like if x = 10 and y = x and changing the value of 'x' or 'y' won't affect the other. This is what happens for the remaining elements of the a and b when you do a shallow copy.
So as in your question when doing b=list(a) and a[1]=0 using a shallow copy behaves as explained above and hence the changes are not reflected in both the list . But the nested listed acts as list assignment like a=[1,2,3] and b=a and making a2=3 will change b2 to 3 as well, i.e.changes in a or b effect both (same as in case 1 above). So this is why in case of a list with in a list any changes reflects in both the list. As in your example doing d=list(c) (here when copying d[2]=c[2] this is similar to list assignment i.e. the reference is copied and in case of list assignment changes are reflected in both so changes to d2 or c2 is reflected in both list) so doing c[2][0] = 0 will also change d[2][0] to zero.
Try the code at http://www.pythontutor.com/visualize.html#mode=edit
to understand better
a=[1,2,"hello",[3,4],5]
b=a
c=a.copy()
a[0]=2
a[3][0]=6
In the both examples, you are creating a shallow copy of the list. The shallow copies essentially copies the aliases to all elements in the first list to the second list.
So you have copied the reference to an [int, int, list, int]. The int elements are immutable, but the list element is mutable. So the third elements both point to the same object in Python's memory. Modifying that object modifies all references to it.

Arrays in Python are assigned by value or by reference?

I am wondering why when I delete the original array it affects the copied array:
arr = [1,2,3]
arr1 = arr
del arr[:]
print(arr1) #this prints []
but when I modify the elements of the original array there is no effect on the copied array:
arr = [1,2,3]
arr1 = arr
arr = [4,5,6]
print(arr1) #this prints [1,2,3]
If someone can explain this issue I appreciate your help, thanks in advance.
You did not modify the elements of the original array, but rather re-assigned a new list to the arr variable. Your intuition of thinking changes to elements would be reflected in arr1 if you properly accessed its elements is indeed true as lists are mutable in Python. For instance,
arr = [1,2,3]
arr1 = arr
arr[1] = 4
print(arr1) #this prints [1,4,3]
Objects in python would be considered to be passed by reference. It's a bit different than that, however.
arr = [1, 2, 3]
This statement does two things. First it creates a list object in memory; second it points the "arr" label to this object.
arr1 = arr
This statement creates a new label "arr1" and points it to the same list object pointed to by arr.
Now in your original code you did this:
del arr[:]
This deleted the elements of the list object and now any label pointing to it will point to an empty list. In your second batch of code you did this:
arr = [4, 5, 6]
This created a new list object in memory, and pointed the "arr" label to it. Now you have two list objects in memory, each being pointed to by two different labels. I just checked on my console, and arr points to [4,5,6] and arr1 to [1,2,3].
Here is a good post about it: http://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dick/
There are several ways to copy an array, but
arr1 = arr
is not one of them (in C-speak, that is just an aliased pointer). Try one of
arr1 = arr[:] # slice that includes all elements, i.e. a shallow copy
arr1 = copy.copy(arr) # from the copy module, does a shallow copy
arr1 = copy.deepcopy(arr) # you guessed it.. ;-)
after any of those you will see that changes to arr1 and arr are independent (of course if you're using a shallow copy then the items in the list will be shared).
You did not do a full copy of the list, instead you just re-assigned the variable name.
In order to make a deep-copy of the list do the following:
arr1 = [x for x in arr]

What is the meaning of arr[:] in assignment in numpy?

I occasionally use numpy, and I'm trying to become smarter about how I vectorize operations. I'm reading some code and trying to understand the semantics of the following:
arr_1[:] = arr_2
In this case,
I understand that in arr[:, 0], we're selecting the first column of the array, but I'm confused about what the difference is between arr_1[:] = arr_2 and arr_1 = arr_2
Your question involves a mix of basic Python syntax, and numpy specific details. In many ways it is the same for lists, but not exactly.
arr[:, 0] returns the 1st column of arr (a view), arr[:,0]=10 sets the values of that column to 10.
arr[:] returns arr (alist[:] returns a copy of a list). arr[:]=arr2 performs an inplace replacement; changing the values of arr to the values of arr2. The values of arr2 will be broadcasted and copied as needed.
arr=arr2 sets the object that the arr variable is pointing to. Now arr and arr2 point to the same thing (whether array, list or anything else).
arr[...]=arr2 also works when copying all the data
Play about with these actions in an interactive session. Try variations in the shape of arr2 to see how values get broadcasted. Also check id(arr) to see the object that the variable points to. And arr.__array_interface__ to see the data buffer of the array. That helps you distinguish views from copies.
arr_1[:] = ... changes the elements of the existing list object that arr_1 refers to.
arr_1 = ... makes the name arr_1 refer to a different list object.
The main difference is what happens if some other name also referred to the original list object. If that's the case, then the former updates the thing that both names refer to; while the latter changes what one name refers to while leaving the other referring to the original thing.
>>> a = [0]
>>> b = a
>>> a[:] = [1]
>>> print(b)
[1] <--- note, change reflected by a and b
>>> a = [2]
>>> print(b)
[1] <--- but now a points at something else, so no change to b
Perhaps it is best to understand by using id to examine the memory location of each variable.
import numpy as np
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
>>> id(arr1)
4595568512
>>> id(arr2)
4595566192
# Slice assignment
arr1[:] = arr2
>>> arr1
array([4, 5, 6])
>>> id(arr1) # The object still points to the same memory location of `arr1`.
4595568512
# Reassignment.
arr1 = arr2
>>> id(arr1) # The object is now pointing to the object located to where `arr2` points.
4595566192
Using arr_1[:] = arr_2 is a shortcut for arr_1.__setitem__(slice(None, None), arr_2). The reason that is used instead of arr_1 = arr_2 is when you use __setitem__, you are modifying arr_1, whereas when you say arr_1 = arr_2, you are redefining arr_1. Using __setitem__, therefore, will modify other references to the arr_1 object rather than just redefining arr_1.

Nested list or Var list?

Im trying to make a nested list work but the problem is that whenever i append a variable it is the same as the first one.
array = [[1,0]]
index = 1
for stuff in list:
array.insert(0,array[0])
array[0][0]+=1
index += 1
if index == 5:
break
print(array)
This returns [[5, 0], [5, 0], [5, 0], [5, 0], [5, 0]]
The weird thing is if i were to make the list into a int it would work.
array = [1]
index = 1
for stuff in array:
array.insert(0,array[0])
array[0]+=1
index += 1
if index == 5:
break
print(array)
This one returns [5, 4, 3, 2, 1]
For the program i am writing i need to remember two numbers. Should i just give up on making it a list or should i make it into two ints or even a tuple? Also is it even possible to do with lists?
I changed list into array same concept though
That is because of this line
list.insert(list[0])
This always refers the list[0] and refereed in all the inserts which you did in the for loop.
And list of integers and list of lists, behave differently.
Also, mention your expected output.
Just to follow up with an explanation from the docs:
Assignment statements in Python do not copy objects, they create
bindings between a target and an object.
So whenever you want to copy objects that contain other objects, like your list contains integers or how a class may contain other members, you should know about this difference (also from the docs):
A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in
the original.
A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.
To achieve a deep copy, as you tried to do in this situation, you can either use Python's built-in list copy method, if you're on Python 3.3 or higher:
deepcopy = list.copy()
Or use the copy module for lower Python versions, which includes a copy.deepcopy() function that returns a deep-copy of a list (or any other compound object).

Categories

Resources