If Two Variables Point to the Same Object, Why Doesn't Reassigning One Variable Affect the Other

If two variables point to the same object, why doesn't reassigning one variable affect the other?

Python has names which refer to objects. Objects exist separately from names, and names exist separately from the objects they refer to.

# name a
a = 1337
# object 1337

When assigning "a name to a name", the right-hand side is evaluated to the referred to object. Similar to how 2 + 2 evaluates to 4, a evaluates to the original 1337.

# name b
b = a
# object referred to by a -> 1337

At this point, we have a -> 1337 and b -> 1337 - note that neither name knows the other! If we test a is b, both names are evaluated to the same object which is obviously equal.

Reassigning a name only changes what that name refers to - there is no connection by which other names could be changed as well.

# name a - reassign
a = 9001
# object 9001

At this point, we have a -> 9001 and b -> 1337. If we now test a is b, both names are evaluated to different objects which are not the same.


If you come from languages such as C, then you are used to variables containing values. For example, char a = 12 can be read as "a is a memory region containing 12". On top, you can have several variables use the same memory. Assigning another value to a variable changes the content of the shared memory - and therefore the value of both variables.

+- char a -+
| 12 |
+--char b -+

# a = -128

+- char a -+
| -128 |
+--char b -+

This is not how Python works: names do not contain anything, but refer to separate values. For example, a = 12 can be read as "a is a name which refers to the value 12". On top, you can have several names refer to the same value - but it will still be separate names, each with its own reference. Assigning another value to a name changes the reference of that name - but leaves the reference of the other name untouched.

+- name a -+ -\
\
--> +- <12> ---+
/ | 12 |
+- name b -+ -/ +----------+

# a = -128
+- <-128> -+
+- name a -+ -----> | -128 |
+----------+

+- <12> ---+
+- name b -+ -----> | 12 |
+----------+

A point of confusion is that mutable objects can appear to violate the separation of names and objects. Commonly, these are containers (e.g list, dict, ...) and classes exhibit the same behaviour by default.

# name m
m = [1337]
# object [1337]
# name n
n = m
# object referred to by m

Similar to a plain integer 1337, a list containing an integer [1337] is an object that can be referred to by several, independent names. As above, n is m evaluates to True and m = [9001] does not change n.

However, certain operations on a name change the value seen by the name and all aliases.

# inplace add to m
m += [9001]

After this operation, m == [1337, 9001] and n is m still holds true. In fact, the value seen by n has also changed to [1337, 9001]. This appears to violate above behaviour, in which aliases did not influence each other.

This is because m += [9001] did not change what m refers to. It only change the content of the list that m (and the alias n) referred to. Both m and n still refer to the original list object, whose value was changed.

+- name m -+ -\
\
--> +- […] -+ +--- <@0> -+
/ | @0 | -> | 1337 |
+- name n -+ -/ +-------+ +----------+

# m += [9001]

+- name m -+ -\
\
--> +- […] -+ +--- <@0> -++--- <@1> -+
/ | @0 @1 | -> | 1337 || 9001 |
+- name n -+ -/ +-------+ +----------++----------+

Why don't two variables referencing the same object change when one of them does?

Think like this.

Both array and String operate based on Addresses.

When you say arr1 = arr2, you are telling that arr1 is going to be pointing to same address location as arr2.

With strings, "XYZ" is also a string, which contains an address location. when you say str1 = str2, str1 will be pointing to same address as str2. But when you say str1 = "XYZ", "XYZ" is another object which is stored at a different address. So, str1 will be reassigned to the new address.

Even with arrays, if you say arr1 = arr3, arr2 won't be changing. arr1 will be repointed to a new address pointed by arr3. But when you say arr1[0], you are actually trying to change the value stored in location, so it will also influence arr2 or arr3 depending on which assignment is latest.

Two dictionary variables point to the same object however they affect eachother

It's not strange behavior. You're simply assigning a new variable to the same object. This is the case for lists and I'm sure other iterable objects in Python. But Here's the way around it. Using deepcopy() ensures that any nested data also gets copied and thus an entirely new object in memory is created.

import copy

first_dict = {'hello' : '1', 'oops': '2'}
second_dict = copy.deepcopy(first_dict)

del first_dict['hello']

print(second_dict)

If I were to use copy.copy(), nested values would not be copied. For example, try this out to see how it behaves:

import copy

first_dict = {
'hello' : '1',
'oops': {
'nested value': 'eh oh',
},
}
second_dict = copy.copy(first_dict)

del first_dict['hello']
del first_dict['oops']['nested value']

print(second_dict)

What happens when you assign the value of one variable to another variable in Python?

As a C++ developer you can think of Python variables as pointers.

Thus when you write spam = 100, this means that you "assign the pointer", which was previously pointing to the object 42, to point to the object 100.

Earlier on, cheese was assigned to point to the same object as spam pointed to, which happened to be 42 at that time. Since you have not modified cheese, it still points to 42.

Immutability has nothing to do with it in this case, since pointer assignment does not change anything about the object being pointed to.

Understanding Mutability and Multiple Variable Assignment to Class Objects in Python

This isn't a matter of immutability vs mutability. This is a matter of mutating an object vs reassigning a reference.

If that object is immutable then when we set two variables to the same object, it'll be two separate copies

This isn't true. A copy won't be made. If you have:

a = 1
b = a

You have two references to the same object, not a copy of the object. This is fine though because integers are immutable. You can't mutate 1, so the fact that a and b are pointing to the same object won't hurt anything.

Python will never make implicit copies for you. If you want a copy, you need to copy it yourself explicitly (using copy.copy, or some other method like slicing on lists). If you write this:

a = b = some_obj

a and b will point to the same object, regardless of the type of some_obj and whether or not it's mutable.


So what's the difference between your examples?

In your first Node example, you never actually alter any Node objects. They may as well be immutable.

slow = fast = head

That initial assignment makes both slow an fast point to the same object: head. Right after that though, you do:

fast = fast.next.next

This reassigns the fast reference, but never actually mutates the object fast is looking at. All you've done is change what object the fast reference is looking at.

In your second example however, you directly mutate the object:

p1.val = 42

While this looks like reassignment, it isn't. This is actually:

p1.__setattr__("val", 42)

And __setattr__ alters the internal state of the object.


So, reassignment changes what object is being looked at. It will always take the form:

a = b  # Maybe chained as well.

Contrast with these that look like reassignment, but are actually calls to mutating methods of the object:

l = [0]
l[0] = 5 # Actually l.__setitem__(0, 5)

d = Dummy()
d.val = 42 # Actually d.__setattr__("val", 42)

Python assigning multiple variables to same value? list behavior

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.

Two reference pointing to the same object in memory but works independently in java?

You are conflating "object" and "reference". For example in the sentence

For example here:

Look at this code in the traverse(),

Node currentNode = this.top;

here an object of type Node is created that points to the already
existing this.top node object.

No object of type Node is created here (there is no new, that's how you know).

What is defined here is a (local) variable of type Node. And the reference that is stored in this.top is also assigned to currentNode.

So it means, two references
pointing to the same object in memory isn't it?

Yes, this part is again correct.

Think of a reference like a sheet of paper. The paper can be empty (i.e. the reference is null) or it can have some address written on it (i.e. it points to some object).

Now currentNode is a piece of paper that happen to have the same address written on it that this.top also has written on it (it's a tiny bit more complicated, because this is a piece of paper written on it and if you look at that address then you'll find some other piece of paper labelled top that has some address written on it, but that doesn't fundamentally change how this works).

At some point later in the code currentNode gets reassigned (i.e. the content of the piece of paper gets changed). First to a different address (i.e. the address scribbled out and replaced with another one) and then eventually with null (i.e. you scribble out the content and leave it "blank").

But just because you wrote on that piece of paper doesn't mean that the other piece of paper (found via this.top) has changed. There is no reason for to it change: they are two independent pieces of paper that at one point happen to have had the same stuff written on them.

Or put differently: assigning a new value to currentNode has absolutely no effect on the object previously referenced by currentNode.

If you had done currentNode.nextLink = null instead of (basically) currentNode = null then that would be different:

  • currentNode = null means "remove the address written on the piece of paper labelled currentNode.
  • currentNode.nextLink = null means "go to the address written on the piece of paper labelled currentNode, there find a piece of paper labelled nextLink and remove the address written on it.

The first one just changes the reference currentNode and the second one actually changes the object pointed to by currentNode.

Edit: it seems your confusion stems from the debugging view where it says currentNode = {StackWithLinkedList$Node@801}. You seem to interpret this as "currentNode is the object {StackWithLinkedList$Node@801}", but that's not what it means.

currentNode is never an object. It can't be. Java variables/fields can't hold objects. So what that display really means is: currentNode currently references the object represented as {StackWithLinkedList$Node@801}.

Why does changing one array alters the other?

Because "a" and "b" reference the same array. There aren't two of them; assigning the value of "a" to "b" assigns the reference to the array, not a copy of the array.

When you assign numbers, you're dealing with primitive types. Even on a Number instance there's no method to update the value.

You can see the same "they're pointing to the same object" behavior with Date instances:

var d1 = new Date(), d2 = d1;
d1.setMonth(11); d1.setDate(25);
alert(d2.toString()); // alerts Christmas day


Related Topics



Leave a reply



Submit