Changing list elements in shallow copy - python

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.

Related

Looping over list to delete items - unexpected behavior [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Removing from a list while iterating over it [duplicate]
(5 answers)
Closed 10 months ago.
A simplified scenario that's not working as expected. I'm hoping someone can point out why.
nums = [0, 0, 4, 0]
new_list = nums
for i in range(len(nums)):
if nums[i] !=0:
break
del new_list[0]
new_list
I'm trying to remove preceding zeroes. I expect the above loop to delete the first element of the list until it encounters a non-zero number, then break. It's only deleting the first zero, even though my del new_list[0] statement is in the loop.
Expected output: [4, 0]
Actual output: [0, 4, 0]
The assignment operator in python (as used in new_list = nums) does not create a new object but just assigns another name to the existing object. Any changes you make to nums will also affect new_list.
To create a new object, you can make a shallow copy instead:
new_list = nums.copy()
In python, assignment operator doesn't make you a new instance of your object, in your casenums. I suggest you to read about deep copy and shallow copy in python. As this article says
In Python, Assignment statements do not copy objects, they create bindings between a target and an object. When we use = operator user thinks that this creates a new object; well, it doesn’t. It only creates a new variable that shares the reference of the original object. Sometimes a user wants to work with mutable objects, in order to do that user looks for a way to create “real copies” or “clones” of these objects. Or, sometimes a user wants copies that user can modify without automatically modifying the original at the same time, in order to do that we create copies of objects.
You can make a deep copy using the copy module.
import copy
nums = [0, 0, 4, 0]
new_list = copy.deepcopy(nums) # using deepcopy for deepcopy
nums = [0, 0, 4, 0]
new_list = nums # this refer to nums and when you change new_list, nums changes
# do following to make a copy.
new_list = nums[::]
for i in range(len(nums)):
if nums[i] !=0:
break
del new_list[0]
new_list
Output
[4, 0]

Why does accessing list items via index in a function work when changing its value, but the iterator variable way doesn't? [duplicate]

This question already has answers here:
How do I operate on the actual object, not a copy, in a python for loop?
(3 answers)
Closed 2 years ago.
I am trying to increment the elements of a list by passing it into a increment() function that I have defined.
I have tried two ways to do this.
Accessing using the index.
# List passed to a function
def increment(LIST):
for i in range(len(LIST)):
LIST[i] += 1
return LIST
li = [1, 2, 3, 4]
li = increment(li)
print(li)
This outputs the desired result: [2, 3, 4, 5]
Accessing using iterator variables.
# List passed to a function
def increment(LIST):
for item in LIST:
item += 1
return LIST
li = [1, 2, 3, 4]
li = increment(li)
print(li)
This outputs: [1, 2, 3, 4]
I wish to know the reason behind this difference.
Python's in-place operators can be confusing. The "in-place" refers to the current binding of the object, not necessarily the object itself. Whether the object mutates itself or creates a new object for the in-place binding, depends on its own implementation.
If the object implements __iadd__, then the object performs the operation and returns a value. Python binds that value to the current variable. That's the "in-place" part. A mutable object may return itself whereas an immutable object returns a different object entirely. If the object doesn't implement __iadd__, python falls back to several other operators, but the result is the same. Whatever the object chooses to return is bound to the current variable.
In this bit of code
for item in LIST:
item += 1
a value of the list is bound to a variable called "item" on each iteration. It is still also bound to the list. The inplace add rebinds item, but doesn't do anything to the list. If this was an object that mutated itself with iadd, its still bound to the list and you'll see the mutated value. But python integers are immmutable. item was rebound to the new integer, but the original int is still bound to the list.
Which way any given object works, you kinda just have to know. Immutables like integers and mutables like lists are pretty straight forward. Packages that rely heavily on fancy meta-coding like pandas are all over the map.
The reasoning behind this is because integers are immutable in python. You are essentially creating a new integer when performing the operation item +=1
This post has more information on the topic
If you wished to update the list, you would need to create a new list or update the list entry.
def increment(LIST):
result = []
for item in LIST:
result.append(item+1)
return result
li = [1, 2, 3, 4]
li = increment(li)
print(li)

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".

Why deepcopy of list of integers returns the same integers in memory?

I understand the differences between shallow copy and deep copy as I have learnt in class. However the following doesn't make sense
import copy
a = [1, 2, 3, 4, 5]
b = copy.deepcopy(a)
print(a is b)
print(a[0] is b[0])
----------------------------
~Output~
>False
>True
----------------------------
Shouldn't print(a[0] is b[0]) evaluate to False as the objects and their constituent elements are being recreated at a different memory location in a deep copy? I was just testing this out as we had discussed this in class yet it doesn't seem to work.
It was suggested in another answer that this may be due to the fact Python has interned objects for small integers. While this statement is correct, it is not what causes that behaviour.
Let's have a look at what happens when we use bigger integers.
> from copy import deepcopy
> x = 1000
> x is deepcopy(x)
True
If we dig down in the copy module we find out that calling deepcopy with an atomic value defers the call to the function _deepcopy_atomic.
def _deepcopy_atomic(x, memo):
return x
So what is actually happening is that deepcopy will not copy an atomic value, but only return it.
By example this is the case for int, float, str, function and more.
The reason of this behavior is that Python optimize small integers so they are not actually in different memory location. Check out the id of 1, they are always the same:
>>> x = 1
>>> y = 1
>>> id(x)
1353557072
>>> id(y)
1353557072
>>> a = [1, 2, 3, 4, 5]
>>> id(a[0])
1353557072
>>> import copy
>>> b = copy.deepcopy(a)
>>> id(b[0])
1353557072
Reference from Integer Objects:
The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behaviour of Python in this case is undefined. :-)
Olivier Melançon's answer is the correct one if we take this as a mechanical question of how the deepcopy function call ends up returning references to the same int objects rather than copies of them. I'll take a step back and answer the question of why that is the sensible thing for deepcopy to do.
The reason we need to make copies of data structures - either deep or shallow copies - is so we can modify their contents without affecting the state of the original; or so we can modify the original while still keeping a copy of the old state. A deep copy is needed for that purpose when a data structure has nested parts which are themselves mutable. Consider this example, which multiplies every number in a 2D grid, like [[1, 2], [3, 4]]:
import copy
def multiply_grid(grid, k):
new_grid = copy.deepcopy(grid)
for row in new_grid:
for i in range(len(row)):
row[i] *= k
return new_grid
Objects such as lists are mutable, so the operation row[i] *= k changes their state. Making a copy of the list is a way to defend against mutation; a deep copy is needed here to make copies of both the outer list and the inner lists (i.e. the rows), which are also mutable.
But objects such as integers and strings are immutable, so their state cannot be modified. If an int object is 13 then it will stay 13, even if you multiply it by k; the multiplication results in a different int object. There is no mutation to defend against, and hence no need to make a copy.
Interestingly, deepcopy doesn't need to make copies of tuples if their components are all immutable*, but it does when they have mutable components:
>>> import copy
>>> x = ([1, 2], [3, 4])
>>> x is copy.deepcopy(x)
False
>>> y = (1, 2)
>>> y is copy.deepcopy(y)
True
The logic is the same: if an object is immutable but has nested components which are mutable, then a copy is needed to avoid mutation to the components of the original. But if the whole structure is completely immutable, there is no mutation to defend against and hence no need for a copy.
* As Kelly Bundy points out in the comments, deepcopy sometimes does make copies of deeply-immutable objects, for example it does generally make copies of frozenset instances. The principle is that it doesn't need to make copies of those objects; it is an implementation detail whether or not it does in some specific cases.

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