understanding python variable assignment [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)
Variable assignment and modification (in python) [duplicate]
(6 answers)
Closed 8 years ago.
I'm trying to teach myself python (and programming in general) and am a little confused about variable assignment. I understand that if I have
>>> a = [1,2,3]
>>> b = a
that b refers to the same object in memory as a does. So if I wanted to create a new list, b, with the same values as a currently has, how would I achieve that?
Also, consider this example:
>>> a = [1, 2, 3]
>>> b = a
>>> x = a[1]
>>> a[1] = 4
>>> print a, b, x
[1, 4, 3] [1, 4, 3] 2
I see from this example, that x is a new object but b points to a. Could someone explain to me what is going on here, why x is a new object but b isn't?

Consider this example:
In [20]: a = [[1], [2], [3]]
In [21]: b = a
In [22]: x = a[1]
In [23]: a
Out[23]: [[1], [2], [3]]
In [24]: b
Out[24]: [[1], [2], [3]]
In [25]: x
Out[25]: [2]
In [26]: a[1][0] = 4
In [27]: a
Out[27]: [[1], [4], [3]]
In [28]: b
Out[28]: [[1], [4], [3]]
In [29]: x
Out[29]: [4]
The difference here is that when we tinkered around with a[1] we did so by modifying it instead of telling a[1] to refer to a whole new thing.
In your case, when you told x to refer to whatever a[1] refers to, it picked up a reference to some concrete thing, whatever was in a[1] at the time, in your case a specific integer.
Later when you told a[1] to change, it did change. But the thing it used to refer to did not stop existing (because x was still there referring to it).
By saying x = a[1] you are not saying x shall always refer to whatever a[1] refers to.
You are saying x shall refer to whatever a[1] refers to at this moment of assignment.
The same is true for b also, it's just that the "concrete" thing that b was told to refer to was a whole list -- which can having changing contents.
Another example:
a = [1, 2, 3,]
b = a
print a, b
# Prints
#[1, 2, 3]
#[1, 2, 3]
a = [4, 5, 6]
print a, b
# Prints
#[4, 5, 6]
#[1, 2, 3]

The answer to the second question is that when you
x = a[1]
x is pointing to the object that is in a[1], not a[1].
When you change a[1] you change the object that a[1] is pointing to, not the object itself.
However, x is still pointing to the old object.
I hope that I explained that clearly. If not comment.
EDIT: Exactly what #jonrsharpe said.

The difference here is that a is a list, which is mutable (i.e. can be changed in place), but a[1] is an int, which is immutable (i.e. can't be changed in place). a[1] = 4 replaces 2 with 4 in the list, but x is still pointing to the 2. -#jonrsharpe
b = a[:]
Will create a clone of a that is a different object. We do this because lists are mutable, so when we are technically taking a section of another list like we are here, it can refer to a new object.

I am also new to python. Based on what I understood so far everything in python is an object and variables are mere references to these objects.
So b = a means b points to the same object as a.
However there are two types of objects mutable and immutable. List is a mutable object, meaning the actual list referenced by a and b in your code can be modified. Hence you see that when you make a change to a you are effectively changing the underlying list this changing b as well.
To create an entirely new list b, you can use b=a[:]

Related

How to assign an element from a 2D array to a new variable without changing its original value in python?

I want to take a value from a 2D array (so this value has a type of array) and assign it to a new variable. But when I make changes to this new variable, its original value in that 2D array also changes. I want to know if there is a correct way that I can do this?
Here is my example code.
a = [[1],[2],[3]]
b = a[1]
b.append(2)
a.append(b)
print(a)
print(b)
In the output, a, b will be
a = [[1], [2, 2], [3], [2, 2]]
b = [2, 2]
Where I don't want a[1] to be changed as well. If someone can help me figure this out?
Thanks.
This is probably one of the most confusing concept for beginner, when you are doing subscription slicing, b = a[1], the reference to first array a has been passed to b, which you whatever action you do to b, will be done to a[1]. unless you make a complete copy of the original array, any action to subscripted will lead to change in original, therefore, whenever you want to work with array without changing its initial value, copy it manually.
a = [[1],[2],[3]]
# in your case it is nested so it will be a bit intricate
ac = list(map(lambda x:list(map(lambda y:y, x)), a))
b = ac[1]
b.append(2)
a.append(b)
print(a)
print(b)
# [[1], [2], [3], [2, 2]]
# [2, 2]
Because a[1] is an array, passing it to b will pass it by reference and not by value.
You can use
b = a[1].copy()
to make a new instance of a[1] without effecting the original a[1].

Multivariable assignment: order_item, created = [duplicate]

I tried to use multiple assignment as show below to initialize variables, but I got confused by the behavior, I expect to reassign the values list separately, I mean b[0] and c[0] equal 0 as before.
a=b=c=[0,3,5]
a[0]=1
print(a)
print(b)
print(c)
Result is:
[1, 3, 5]
[1, 3, 5]
[1, 3, 5]
Is that correct? what should I use for multiple assignment?
what is different from this?
d=e=f=3
e=4
print('f:',f)
print('e:',e)
result:
('f:', 3)
('e:', 4)
If you're coming to Python from a language in the C/Java/etc. family, it may help you to stop thinking about a as a "variable", and start thinking of it as a "name".
a, b, and c aren't different variables with equal values; they're different names for the same identical value. Variables have types, identities, addresses, and all kinds of stuff like that.
Names don't have any of that. Values do, of course, and you can have lots of names for the same value.
If you give Notorious B.I.G. a hot dog,* Biggie Smalls and Chris Wallace have a hot dog. If you change the first element of a to 1, the first elements of b and c are 1.
If you want to know if two names are naming the same object, use the is operator:
>>> a=b=c=[0,3,5]
>>> a is b
True
You then ask:
what is different from this?
d=e=f=3
e=4
print('f:',f)
print('e:',e)
Here, you're rebinding the name e to the value 4. That doesn't affect the names d and f in any way.
In your previous version, you were assigning to a[0], not to a. So, from the point of view of a[0], you're rebinding a[0], but from the point of view of a, you're changing it in-place.
You can use the id function, which gives you some unique number representing the identity of an object, to see exactly which object is which even when is can't help:
>>> a=b=c=[0,3,5]
>>> id(a)
4473392520
>>> id(b)
4473392520
>>> id(a[0])
4297261120
>>> id(b[0])
4297261120
>>> a[0] = 1
>>> id(a)
4473392520
>>> id(b)
4473392520
>>> id(a[0])
4297261216
>>> id(b[0])
4297261216
Notice that a[0] has changed from 4297261120 to 4297261216—it's now a name for a different value. And b[0] is also now a name for that same new value. That's because a and b are still naming the same object.
Under the covers, a[0]=1 is actually calling a method on the list object. (It's equivalent to a.__setitem__(0, 1).) So, it's not really rebinding anything at all. It's like calling my_object.set_something(1). Sure, likely the object is rebinding an instance attribute in order to implement this method, but that's not what's important; what's important is that you're not assigning anything, you're just mutating the object. And it's the same with a[0]=1.
user570826 asked:
What if we have, a = b = c = 10
That's exactly the same situation as a = b = c = [1, 2, 3]: you have three names for the same value.
But in this case, the value is an int, and ints are immutable. In either case, you can rebind a to a different value (e.g., a = "Now I'm a string!"), but the won't affect the original value, which b and c will still be names for. The difference is that with a list, you can change the value [1, 2, 3] into [1, 2, 3, 4] by doing, e.g., a.append(4); since that's actually changing the value that b and c are names for, b will now b [1, 2, 3, 4]. There's no way to change the value 10 into anything else. 10 is 10 forever, just like Claudia the vampire is 5 forever (at least until she's replaced by Kirsten Dunst).
* Warning: Do not give Notorious B.I.G. a hot dog. Gangsta rap zombies should never be fed after midnight.
Cough cough
>>> a,b,c = (1,2,3)
>>> a
1
>>> b
2
>>> c
3
>>> a,b,c = ({'test':'a'},{'test':'b'},{'test':'c'})
>>> a
{'test': 'a'}
>>> b
{'test': 'b'}
>>> c
{'test': 'c'}
>>>
In python, everything is an object, also "simple" variables types (int, float, etc..).
When you changes a variable value, you actually changes it's pointer, and if you compares between two variables it's compares their pointers.
(To be clear, pointer is the address in physical computer memory where a variable is stored).
As a result, when you changes an inner variable value, you changes it's value in the memory and it's affects all the variables that point to this address.
For your example, when you do:
a = b = 5
This means that a and b points to the same address in memory that contains the value 5, but when you do:
a = 6
It's not affect b because a is now points to another memory location that contains 6 and b still points to the memory address that contains 5.
But, when you do:
a = b = [1,2,3]
a and b, again, points to the same location but the difference is that if you change the one of the list values:
a[0] = 2
It's changes the value of the memory that a is points on, but a is still points to the same address as b, and as a result, b changes as well.
Yes, that's the expected behavior. a, b and c are all set as labels for the same list. If you want three different lists, you need to assign them individually. You can either repeat the explicit list, or use one of the numerous ways to copy a list:
b = a[:] # this does a shallow copy, which is good enough for this case
import copy
c = copy.deepcopy(a) # this does a deep copy, which matters if the list contains mutable objects
Assignment statements in Python do not copy objects - they bind the name to an object, and an object can have as many labels as you set. In your first edit, changing a[0], you're updating one element of the single list that a, b, and c all refer to. In your second, changing e, you're switching e to be a label for a different object (4 instead of 3).
You can use id(name) to check if two names represent the same object:
>>> a = b = c = [0, 3, 5]
>>> print(id(a), id(b), id(c))
46268488 46268488 46268488
Lists are mutable; it means you can change the value in place without creating a new object. However, it depends on how you change the value:
>>> a[0] = 1
>>> print(id(a), id(b), id(c))
46268488 46268488 46268488
>>> print(a, b, c)
[1, 3, 5] [1, 3, 5] [1, 3, 5]
If you assign a new list to a, then its id will change, so it won't affect b and c's values:
>>> a = [1, 8, 5]
>>> print(id(a), id(b), id(c))
139423880 46268488 46268488
>>> print(a, b, c)
[1, 8, 5] [1, 3, 5] [1, 3, 5]
Integers are immutable, so you cannot change the value without creating a new object:
>>> x = y = z = 1
>>> print(id(x), id(y), id(z))
507081216 507081216 507081216
>>> x = 2
>>> print(id(x), id(y), id(z))
507081248 507081216 507081216
>>> print(x, y, z)
2 1 1
in your first example a = b = c = [1, 2, 3] you are really saying:
'a' is the same as 'b', is the same as 'c' and they are all [1, 2, 3]
If you want to set 'a' equal to 1, 'b' equal to '2' and 'c' equal to 3, try this:
a, b, c = [1, 2, 3]
print(a)
--> 1
print(b)
--> 2
print(c)
--> 3
Hope this helps!
What you need is this:
a, b, c = [0,3,5] # Unpack the list, now a, b, and c are ints
a = 1 # `a` did equal 0, not [0,3,5]
print(a)
print(b)
print(c)
Simply put, in the first case, you are assigning multiple names to a list. Only one copy of list is created in memory and all names refer to that location. So changing the list using any of the names will actually modify the list in memory.
In the second case, multiple copies of same value are created in memory. So each copy is independent of one another.
The code that does what I need could be this:
# test
aux=[[0 for n in range(3)] for i in range(4)]
print('aux:',aux)
# initialization
a,b,c,d=[[0 for n in range(3)] for i in range(4)]
# changing values
a[0]=1
d[2]=5
print('a:',a)
print('b:',b)
print('c:',c)
print('d:',d)
Result:
('aux:', [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
('a:', [1, 0, 0])
('b:', [0, 0, 0])
('c:', [0, 0, 0])
('d:', [0, 0, 5])
To assign multiple variables same value I prefer list
a, b, c = [10]*3#multiplying 3 because we have 3 variables
print(a, type(a), b, type(b), c, type(c))
output:
10 <class 'int'> 10 <class 'int'> 10 <class 'int'>
Initialize multiple objects:
import datetime
time1, time2, time3 = [datetime.datetime.now()]*3
print(time1)
print(time2)
print(time3)
output:
2022-02-25 11:52:59.064487
2022-02-25 11:52:59.064487
2022-02-25 11:52:59.064487
E.g: basically a = b = 10 means both a and b are pointing to 10 in the memory, you can test by id(a) and id(b) which comes out exactly equal to a is b as True.
is matches the memory location but not its value, however == matches the value.
let's suppose, you want to update the value of a from 10 to 5, since the memory location was pointing to the same memory location you will experience the value of b will also be pointing to 5 because of the initial declaration.
The conclusion is to use this only if you know the consequences otherwise simply use , separated assignment like a, b = 10, 10 and won't face the above-explained consequences on updating any of the values because of different memory locations.
The behavior is correct. However, all the variables will share the same reference. Please note the behavior below:
>>> a = b = c = [0,1,2]
>>> a
[0, 1, 2]
>>> b
[0, 1, 2]
>>> c
[0, 1, 2]
>>> a[0]=1000
>>> a
[1000, 1, 2]
>>> b
[1000, 1, 2]
>>> c
[1000, 1, 2]
So, yes, it is different in the sense that if you assign a, b and c differently on a separate line, changing one will not change the others.
Here are two codes for you to choose one:
a = b = c = [0, 3, 5]
a = [1, 3, 5]
print(a)
print(b)
print(c)
or
a = b = c = [0, 3, 5]
a = [1] + a[1:]
print(a)
print(b)
print(c)

Python append function is not working as expected

>>> a = [1,2,3]
>>> b = []
>>> b.append(a)
>>> print(b)
[[1, 2, 3]]
>>> num = a.pop(0)
>>> a.append(num)
>>> print(a)
[2, 3, 1]
>>> b.append(a)
>>> print(b)
[[2, 3, 1], [2, 3, 1]]
>>>
Why is this happening and how to fix it? I need the list like
[[1, 2, 3], [2, 3, 1]]
Thank you.
Edit:
Also, why is this working?
>>> a = []
>>> b = []
>>> a = [1,2,3]
>>> b.append(a)
>>> a = [1,2,3,4]
>>> b.append(a)
>>> print(b)
[[1, 2, 3], [1, 2, 3, 4]]
>>>
'''
Append a copy of your list a, at least the first time. Otherwise, you've appended the same list both times.
b.append(a[:])
When you append the list a, python creates a reference to that variable inside the list b. So when you edit the list a, it is reflected again in the list b. You need to create a copy of your variable and then append it to get the desired result.
Every variable name in Python should be thought of as a reference to a piece of data. In your first listing, b contains two references to the same underlying object that is also referenced by the name a. That object gets changed in-place by the operations you’re using to rotate its members. The effect of that change is seen when you look at either of the two references to the object found in b, or indeed when you look at the reference associated with the name a.
Their identicality can be seen by using the id() function: id(a), id(b[0]) and id(b[1]) all return the same number, which is the unique identifier of the underlying list object that they all refer to. Or you can use the is operator: b[0] is b[1] evaluates to True.
By contrast, in the second listing, you reassign a—in other words, by using the assignment operator = you cause that name to become associated with a different object: in this case, a new list object that you just created with your square-bracketed literal expression. b still contains one reference to the old list, and now you append a new reference that points to this different piece of underlying data. So the two elements of b now look different from each other—and indeed they are different objects and accordingly have different id() numbers, only one of which is the same as the current id(a). b[0] is b[1] now evaluates to False
How to fix it? Reassign the name a before changing it: for example, create a copy:
a = list(a)
or:
import copy
a = copy.copy(a)
(or you could even use copy.deepcopy()—study the difference). Alternatively, rotate the members a using methods that entail reassignment rather than in-place changes—e.g.:
a = a[1:] + a[:1]
(NB immutable objects such as the tuple avoid this whole confusion —not because they behave fundamentally differently but because they lack methods that produce in-place changes and therefore force you to use reassignment strategies.)
In addition to making the copy of a by doing a[:] and assigning it to b.
You can also use collections.deque.rotate to rotate your list
from collections import deque
a = [1,2,3]
#Make a deque of copy of a
b = deque(a[:])
#Rotate the deque
b.rotate(len(a)-1)
#Create the list and print it
print([a,list(b)])
#[[1, 2, 3], [2, 3, 1]]

Elements of the original list changes when a deep copy of changes [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 7 years ago.
I have a global list (of lists) variable. I send a shallow copy of the global list to another function as a parameter.
Surprisingly, the original list gets changed when I remove some elements from the parameter within the invoked function.
Can someone please tell me why it's happening and how to prevent this from happening?
Here is the simplified code example:
def saveCandidateRoutes(candidateRoutes):
for route in candidateRoutes:
if route: # check if the list, 'route', is empty.
tweetId = route.pop(0)
stanox = route.pop(-1)
....
def main():
global allCandidatePaths
copyOfAllCandidatePaths= list(allCandidatePaths) # making a deep copy
result = saveCandidateRoutes(copyOfAllCandidatePaths)
I think you need a quick reminder on shallow and deep copies, and how to make a deep copy.
>>> a = [[1,2], [3,4]] # a list of mutable elements
>>> b = a[:]
>>> c = list(a)
Both b and c are shallow copies of a, you can check that a, b and c are different objects because they do not share the same id.
>>> id(a)
140714873892592
>>> id(b)
140714810215672
>>> id(c)
140714873954744
However, each element of a, b and c still is a reference to the lists [1,2] and [3,4] we created when defining a. That becomes clear when we mutate an item inside the lists:
>>> c[1][1] = 42
>>> a
[[1, 2], [3, 42]]
>>> b
[[1, 2], [3, 42]]
>>> c
[[1, 2], [3, 42]]
As you can see, the second element of the second list changed in a, b and c.
Now, to make a deep copy of a, you have several options. One is a list comprehension where you copy each of the sublists:
>>> d = [sublist[:] for sublist in a]
>>> d
[[1, 2], [3, 42]]
>>> d[1][1] = 23
>>> d
[[1, 2], [3, 23]]
>>> a
[[1, 2], [3, 42]]
As you can see, the 42 in a did not change to 23, because the second list in a and d are different objects:
>>> id(a[1])
140714873800968
>>> id(d[1])
140714810230904
Another way to create a deep copy is with copy.deepcopy:
>>> from copy import deepcopy
>>> e = deepcopy(a)
>>> e
[[1, 2], [3, 42]]
>>> e[1][1] = 777
>>> e
[[1, 2], [3, 777]]
>>> a
[[1, 2], [3, 42]]
python uses references to object, it means that both allCandidatePaths and candidateRoutes point to the same list in memory, and you can use both of them to change the list.
To prevent this from happening, in the beginning of your function saveCandidateRoutes add this instruction candidateRoutes = list(candidateRoutes). The list() function will create another copy of your original list in memory and assign its reference to candidateRoutes.
So when you use candidateRoutes, you will not be working on your original list that is in the main function, but you will be working on another list.
I think you are confused about the terms.
Shallow copy is the type of copy where the elements of the copied list are still bound to same memory value with the original list's elements.
What you are looking for is deepcopy
Here is a good source to find out.
And also:
Wikipedia
A shallow copy copies the object but not any of its attributes. For a list, that means that that its elements are assigned to the elements of the new list. If the elements are ints, you get a completely new list, because int is a primitive type*, so it doesn't need to be copied. If the elements are lists, as in your two-dimesional list, they get assigned to the copy, so you have two references to each element, one in each list. If you want to copy the elements of the inner lists, you'll need a deep copy, which recursively copies the attributes (elements, in this case) of each object.
*There isn't actually a distinction between types that are primitive and those that aren't, but this is equivalent to the way it works in the scope of this explanation.

What exactly is happening when I copy and then edit this list?

I cannot figure out at all why this is happening:
A = [[1,0], [2,2]]
B = list(A)
print('start A:', A, 'start B:', B)
A[0][0] = 999
print('end A:', A, 'end B:', B)
This returns:
start A: [[1, 0], [2, 2]] start B: [[1, 0], [2, 2]]
end A: [[999, 0], [2, 2]] end B: [[999, 0], [2, 2]]
The lists A and B end up being the same, even though I explicitly copied B from A. This only happens when I do something like A[0][0] = 999; if I replace that with A[0] = 999 then A and B are different at the end.
What's the reason behind this, and is there any way to change A in this manner without affecting B?
You are creating a shallow copy of the original list, that is a new list containing new references to the same objects as the original list.
Modifying the new list object does not alter the original list. Modifying the objects in the new list does modify the objects in the old list because they are the same.
To get a completely separate list, use copy.deepcopy() to create a deep copy.
Both A and B contain the same two lists.
Your code is roughly equivalent to this:
x = [1, 0]
y = [2, 2]
A = [x, y]
B = [x, y]
The operation A[0][0] = 999 is effectively just doing x[0] = 999. That is, it doesn't modify A itself, it modifies the first element of the list x. Since both A and B have references to x, both will see the change.
A simple copy operation like you did is shallow, it only copies the items one level deep and does not recurse into nested structures. You need
>>> import copy
>>> A = [[1,0], [2,2]]
>>> B = copy.deepcopy(A)
>>> print('start A:', A, 'start B:', B)
start A: [[1, 0], [2, 2]] start B: [[1, 0], [2, 2]]
>>> A[0][0] = 999
>>> print('end A:', A, 'end B:', B)
end A: [[999, 0], [2, 2]] end B: [[1, 0], [2, 2]]
A and B are two different names for the same chunk of memory within your computer.
A and B are two separate list objects, but A[0] and B[0] are two different names for the same chunk of memory within your computer. Try the following from the interpreter:
id(B)
id(A)
id(B[0])
id(A[0])
Python code manipulates references to objects.
Assigning to a variable is just binding a name to refer to an object.
A list consists of a bunch of references to objects. list(A) finds all the objects referenced in A and makes a new list with references to all the same objects. So if A is a list of lists, list(A) makes a new list with references to the same lists that were in A. So changing any of the sub-lists will be visible from both A and the new list.
copy.deepcopy exists to help you get around this, when you need a full "deep" copy of something.
Once you learn to think about Python code as manipulating references to objects like this, you will intuitively understand when code is likely to end up referring to the same object from multiple places like this, though there will probably always be obscure cases that surprise you.

Categories

Resources