Does Python automatically update variables whose value is another object? [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)
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.

Related

Python for loop weird bug [duplicate]

This question already has answers here:
Why can I use a list index as an indexing variable in a for loop? [duplicate]
(6 answers)
Are for-loop name list expressions legal?
(2 answers)
Closed last year.
So in a quiz, I have given the question what the execution of this block of code gives (The one under).
a = [0, 1, 2, 3]
for a[0] in a:
print(a[0])
I had selected error but to my surprise, it actually works and is able to print all the elements inside the list. But how?
firstly the element getting variable (usually i) is shadowing the actual variable on top of that we are getting the first element inside a number so how is it working.
a[0] is type of a so it can be used to iterate over the array but as in look you are assigning value in a[0] it's value will change to last element of the array.
a = [0,1,2,3]
for a[0] in a:
print(a[0]
Will result in:
0
1
2
3
But now printing a will give you modified array:
print(a)
[3, 1, 2, 3]

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)

Appending a global list to a global list? [duplicate]

This question already has answers here:
Why does foo.append(bar) affect all elements in a list of lists?
(3 answers)
Value changes in new list impact previous list values, within a list of lists [duplicate]
(2 answers)
Python append behaviour odd? [duplicate]
(3 answers)
Closed 4 years ago.
I have written following code for solving the n-Queens problem where we have to find all possible legal (non-attacking) placements of n queens in an n*n chessboard. The code uses the standard backtracking solution.
Here the method n_queens uses the helper method solve_n_queens which uses recursion. The outer method just initializes global lists result & col_placement and calls the helper method.
def n_queens(n):
def solve_n_queens(row):
if row == n: # all queens are legally placed
result.append(list(col_placement))
return
for col in range(n):
# check if new queen is either 1) in same column or 2) same diagonal with any previously placed queen
if all(abs(col-c) not in (0, row-r)
for r, c in enumerate(col_placement[:row])):
col_placement[row] = col
solve_n_queens(row+1)
result, col_placement = [], [0] * n # result is empty initially; [0] * n means no queen is placed
solve_n_queens(0)
return result
This gives erroneous output for n_queens(4)
[[3, 1, 2, 1], [3, 1, 2, 1]]
However it is not an algorithmic bug because just altering the 4th line result.append(col_placement) to result.append(list(col_placement)) mysteriously gives the correct output
[[1, 3, 0, 2], [2, 0, 3, 1]]
What I don't grok is when col_placement is already a list, why do we need to call the list method?
The problem is that without using list you are appending a reference to the same and only list col_placement you are working with (as you can see the results are not only wrong, but they are also the same). Using list creates a new copy (a instant snapshot of col_placement) that will not be modified when col_placement does as you keep going through the rest of the program.
So essentially list(col_placement) is the same as col_placement.copy() or col_placement[:].

python np array change element value in for loop

I have a question about the element value change in np 2D-array. Here I have a example:
a=np.arange(10).reshape(2,5)
for i in a: # go through the rows of array
i=np.array([0,0,0,0,0])
print a
The return value is
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
which means there is no change to the original array. Does it mean the i in the for loop is a copy of each row of array a? If i is the copy, then it makes sense because the change of a copy will not affect the original value. But I try the following code:
a=np.arange(10).reshape(2,5)
for i in a: # go through the rows of array
i[:]=np.array([0,0,0,0,0])
print a
The return result is
array([[0, 0, 0, 0, 0],
0, 0, 0, 0, 0]])
so I don'y understand why i[:] can work here if the i is the copy. If this question is duplicated, could you please provide the link?
Thanks.
i is not a copy. Keep in mind that for loops make repetitive assignments from the items in the iterable to the target list (on the left side of in). So at each iteration, i holds a reference to each subarray in a.
Here:
for i in a:
i = np.array([0,0,0,0,0])
You assign each subarray in a to the name i and immediately afterwards assign the name to an entirely unrelated object. Try i = 'unrelated' in that loop, and notice that has no effect (I mean on a) other than assign i to a new string object.
The second case assigns the subarrays in a to i in succession (like the first case) but afterwards performs an in place modification via the reference i.
More clearly, the first iteration of the second case is same as:
i = a[0]
i[:] = np.array([0,0,0,0,0])
And the second iteration:
i = a[1]
i[:] = np.array([0,0,0,0,0])
Notice also how in the transition from the first to second iteration i = a[1] does not modify the previous reference a[0] but instead reassigns the name i to a new object a[1].

python passing argument to a function

folks, i got a question about passing mutable object to a function
with the following code, I was expecting the output to be [0,0,0], while the output is [0,1,2,3]
does it mean the argument is actually copied and then send to the inside of the function?
def lala(a):
n = [0, 0 , 0]
a = n
a = [0,1,2,3]
lala(a)
print a
if i want to fulfill the above task inside the function, how shall i write it elegantly?
thanks very much!
Python makes more sense if you think of attaching name tags to objects, rather than stuffing objects into named boxes.
def lala(a):
n = [0, 0 , 0]
a = n
Here's what's happening.
You're receiving a parameter (a list in this case) and giving it the name a.
You're creating a new list and giving it the name n.
You are giving the list you named n the additional name a.
Both names, a and n, are local to the lala function, so they "expire" when the function ends.
The list you created in the function now has no names, and Python discards it.
In other words, a is not a box into which you can put a new list. It is a name you gave a list you received. Later, you reassign that name to the list you have also named n.
Others have suggested a slice assignment, a[:] = n, which uses a reference to the items in the list rather than the list itself. When you do this, the name a still points to the same list; its contents have just been replaced. Since it is the same list that was passed into the function, any names by which it is known outside the function will "see" the new contents.
kindall provided a great explanation in my opinion, but my preferred method for doing something like this is to have the function return the change instead of messing with references.
def lala():
n = [0, 0, 0]
return n
a = [0, 1, 2, 3]
a = lala()
print a # prints [0, 0, 0]
you can use a[:] = n inside the function. this is called a slice assignment
python does not have a call-by-reference feature. There's no general way to do this.
If you know your argument is going to be a list, and you want it to take a different value, you can write it like so:
def lala(a):
a[:] = [0,0,0]
That's because a function makes new label "a" for its scope. Then this "a" overshadows the "a" you defined outside the function. So the new label a is assigned to new object [0,0,0,0]
If you would write:
def lala(a):
a.pop()
a = [0,1,2,3,4]
lala(a)
print(a)
You would see that a = [0,1,2,3] because pop() actually change the object which label 'a' points to. (while assignment just change to what object given label points to)
Note the difference between your function, and this one:
def lala(a):
a[0] = 7
a = [0,1,2,3]
lala(a)
print a # prints [7, 1, 2, 3]

Categories

Resources