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

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]

Related

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)

Does Python automatically update variables whose value is another object? [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 3 years ago.
Before asking, I read the accepted answer to the question "How do I pass a variable by reference?" and documentation linked in that same answer: "How do I write a function with output parameters (call by reference)?"
I have a related question: Does Python automatically synchronize a variable whose value is a reference to another object? In other words, if I assign an object as the value of a variable, is the variable updated whenever the object is modified?
I have a specific problem where it appears that Python updates the value of a variable with an object as its value without any code explicitly triggering an update. I created a function that was supposed to be part of a solution to the ROT13 (rotate right 13 times) problem: shift an array to the right 13 times. Here is the function's code:
array = [0, 1, 2, 3, 4, 5]
print(array)
backup = array
#backup = [n for n in array]
for i in range( 1, (len(backup)) ):
array[i] = backup[i - 1]
array[0] = backup[-1]
backup = array
print(array)
The output of that code is wrong: [0, 0, 0, 0, 0, 0] .
However, when I replace line 3 (backup = array) with backup = [n for n in array], the answer is correct: [5, 0, 1, 2, 3, 4]
I inferred that whenever the for-loop executed, the value of backup was updated because its value is inherently a reference to the object array. It appears to me that when array[1] was assigned the value zero, backup[1] was also assigned zero instead of holding the value 1. Because of that, the for-loop simply assigned the value zero to every other variable in backup thru array.
If I instead assigned backup to a list object distinct from array using backup = [n for n in array], modifying array would not modify backup.
What is the actual cause of this behavior?
In your example backup and array are both references to the same object. That is clear with this code example:
>>> array=[1,2,3,4]
>>> backup=array
>>> id(array)
4492535840
>>> id(backup)
4492535840
So your code is equivalent to this:
array = [0, 1, 2, 3, 4, 5]
print(array)
for i in range( 1, (len(array)) ):
array[i] = array[i - 1]
array[0] = array[-1]
print(array)
Does that help?
There’s no synchronization going on. Instead, there’s only one list. Both variables reference that same list – you can think of the variables as pointing to it or as tagging it, if that helps. You perform operations on values and not on variables, so given that there’s only one list, all of the operations change it and read changes back from the same one.

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.

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