Variable assignment and modification (in python)
Memory management in Python involves a private heap memory location containing all Python objects and data structures.
Python's runtime only deals in references to objects (which all live in the heap): what goes on Python's stack are always references to values that live elsewhere.
>>> a = [1, 2]
>>> b = a
>>> a.append(3)
Here we can clearly see that the variable b
is bound to the same object as a
.
You can use the is
operator to tests if two objects are physically the same, that means if they have the same address in memory. This can also be tested also using the id()
function.
>>> a is b
>>> True
>>> id(a) == id(b)
>>> True
So, in this case, you must explicitly ask for a copy.
Once you've done that, there will be no more connection between the two distinct list objects.
>>> b = list(a)
>>> a is b
>>> False
Variable assignment and modification with regards to memory addresses
The behavior in the example is as follows
In [1]: a = 1
In [2]: b = 2
In [3]: a = b
In [4]: b+=1
In [5]: b
Out[5]: 3
In [6]: a
Out[6]: 2
In the example, when you do a=b
, both a and b are pointing to the same reference, but when you b += 1
, the operation of adding 1 to b, creates a new integer value 3
for b and b points to that value, but a is still pointing to the old value 2
Note that trying to do it with mutable types like a list works as what you were excepting would happen to an integer
In [1]: a = [1]
In [2]: b = [2]
In [3]: a = b
In [4]: b.append(2)
In [5]: a
Out[5]: [2, 2]
In [6]: b
Out[6]: [2, 2]
In [7]: b += [3, 4];
In [8]: b
Out[8]: [2, 2, 3, 4]
In [9]: a
Out[9]: [2, 2, 3, 4]
Now what happened here? We changed b
but a
changed as well, this is because the append
or update of the list happens in-place
and since a
was pointing to b
both ends up getting updated!
What happens for +=
operator is defined by __iadd__
method of a class. For int
-s all the __iXXX__
methods return a new instance of int. For list
-s __iadd__(self, other)
does self.extend(other); return self
, so variable keeps pointing to same object.
As a result, even integers can be made to behave as a list, an example is here courtesy @imposeren
Python Variable Assignment
When you assign b with a, both objects now point to the same memory location. So any changes done in either of the object will reflect in the other. The objects memory addresses become different when you assign 'b' with a new value. To elaborate:
>>> a=[1,2,3]
>>> b=a
>>> id(a)
4520124856
>>> id(b)
4520124856
>>> b=[111]
>>> id(a)
4520124856
>>> id(b)
4520173936
>>>
id() is used to obtain an object's memory address.
If you want to create a exact replica of 'a' without it having he same memory location, use b=a[:] which will create a copy of the data only.
when modification of the assigned variable will affect the value of original variable?
You should consider that all variables in python are indeed pointers to objects.
Case 1
When you write
x = 10
the variable x
is a pointer to a number object with value 10
.
y = x
the variable y
is a pointer to the same object currently pointed by x
.
y = 100
now the variable y
is instead pointing to another number object with value 100
. This clearly has no effect on the object that x
is pointing to.
Case 2
When you write
x = [1, 2]
x
is pointing to an array object that contains two pointers to number objects with value 1
and 2
.
y = x
y
is now pointing to the same array as x
y.append(3)
this doesn't affect the variable y
(this is the key point!), but alter the object it is pointing to (the array) by adding another element. Because x
is also pointing to the same object the change will be visible from x
too.
How to re-assign (or modify) a Python variable defined using another variable without modifying both
This will work.
list1 = [1,2,3]
list2 = list1[:]
Now, list2.append(4)
---> will not affect list1
.
Your first attempt is only copying the references so that list1
and list2
will refer to the same variable.
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)
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.
Python variable assignment between different methods
I am not sure what you try to do, since you give the functions no input the values of x will always be the same but see this:
class MyClass:
# Variable x definition
def __init__(self):
self.x = None
def Method1(self):
self.x = 1
print('X VALUE: ', self.x)
def Method2(self):
print('X VALUE BEFORE NEW ASSIGNMENT: ', self.x)
self.x = 2
print('X VALUE NEW ASSIGNMENT: ', self.x)
c = MyClass()
c.Method1()
c.Method2()
c.Method2()
output:
X VALUE: 1
X VALUE BEFORE NEW ASSIGNMENT: 1
X VALUE NEW ASSIGNMENT: 2
X VALUE BEFORE NEW ASSIGNMENT: 2
X VALUE NEW ASSIGNMENT: 2
How to re-assign a variable in python without changing its id?
I'm not sure whether you're confused about variables in Python, or about immutable values. So I'm going to explain both, and half the answer will probably seem like "no duh, I already knew that", but the other half should be useful.
In Python—unlike, say, C—a variable is not a location where values live. It's just a name. The values live wherever they want to.1 So, when you do this:
a = 10
b = a
You're not making b
into a reference to a
. That idea doesn't even make sense in Python. You're making a
into a name for 10
, and then making b
into another name for 10
. And if you later do this:
a = 11
… you've made a
into a name for 11
, but this has no effect on b
—it's still just a name for 10
.
This also means that id(a)
is not giving you the ID of the variable a
, because there is no such thing. a
is just a name that gets looked up in some namespace (e.g., a module's globals dict). It's the value, 11
(or, if you ran it earlier, the different value 10
) that has an ID. (While we're at it: it's also values, not variables, that are typed. Not relevant here, but worth knowing.)
Things get a bit tricky when it comes to mutability. For example:
a = [1, 2, 3]
b = a
This still makes a
and b
both names for a list.
a[0] = 0
This doesn't assign to a
, so a
and b
are still names for the same list. It does assign to a[0]
, which is part of that list. So, the list that a
and b
both name now holds [0, 2, 3]
.
a.extend([4, 5])
This obviously does the same thing: a
and b
now name the list [0, 2, 3, 4, 5]
.
Here's where things get confusing:
a += [6]
Is it an assignment that rebinds a
, or is it just mutating the value that a
is a name for? In fact, it's both. What this means, under the covers, is:
a = a.__iadd__([6])
… or, roughly:
_tmp = a
_tmp.extend([6])
a = _tmp
So, we are assigning to a
, but we're assigning the same value back to it that it already named. And meanwhile, we're also mutating that value, which is still the value that b
names.
So now:
a = 10
b = 10
a += 1
You probably can guess that the last line does something like this:
a = a.__iadd__(1)
That's not quite true, because a
doesn't define an __iadd__
method, so it falls back to this:
a = a.__add__(1)
But that's not the important bit.2 The important bit is that, because integers, unlike lists, are immutable. You can't turn the number 10 into the number 11 the way you could in INTERCAL or (sort of) Fortran or that weird dream you had where you were the weirdest X-Man. And there's no "variable holding the number 10" that you can set to 11, because this isn't C++. So, this has to return a new value, the value 11
.
So, a
becomes a name for that new 11
. Meanwhile, b
is still a name for 10
. It's just like the first example.
But, after all this telling you how impossible it is to do what you want, I'm going tell you how easy it is to do what you want.
Remember earlier, when I mentioned that you can mutate a list, and all the names for that list will see the new value? So, what if you did this:
a = [10]
b = a
a[0] += 1
Now b[0]
is going to be 11
.
Or you can create a class:
class Num:
pass
a = Num()
a.num = 10
b = a
a.num += 1
Now, b.num
is 11
.
Or you can even create a class that implements __add__
and __iadd__
and all the other numeric methods, so it can hold numbers (almost) transparently, but do so mutably.
class Num:
def __init__(self, num):
self.num = num
def __repr__(self):
return f'{type(self).__name__}({self.num})'
def __str__(self):
return str(self.num)
def __add__(self, other):
return type(self)(self.num + other)
def __radd__(self, other):
return type(self)(other + self.num)
def __iadd__(self, other):
self.num += other
return self
# etc.
And now:
a = Num(10)
b = a
a += 1
And b
is a name for the same Num(11)
as a
.
If you really want to do this, though, you should consider making something specific like Integer
rather than a generic Num
that holds anything that acts like a number, and using the appropriate ABC in the numbers
module to verify that you covered all the key methods, to get free implementations for lots of optional methods, and to be able to pass isinstance
type checks. (And probably call num.__int__
in its constructor the way int
does, or at least special-case isinstance(num, Integer)
so you don't end up with a reference to a reference to a reference… unless that's what you want.)
1. Well, they live wherever the interpreter wants them to live, like Romanians under Ceaușescu. But if you're a builtin/extension type written in C and a paid-up member of the Party, you could override __new__
with a constructor that doesn't rely on super
to allocate, but otherwise you have no choice.
2. But it's not completely unimportant. By convention (and of course in all builtin and stdlib types follow the convention), __add__
doesn't mutate, __iadd__
does. So, mutable types like list
define both, meaning they get in-place behavior for a += b
but copying behavior for a + b
, while immutable types like tuple
and int
define only __add__
, so they get copying behavior for both. Python doesn't force you to do things this way, but your type would be very strange if it didn't pick one of those two. If you're familiar with C++, it's the same—you usually implement operator+=
by mutating in-place and returning a reference to this
, and operator+
by copying and then returning +=
on the copy, but the language doesn't force you to, it's just confusing if you don't.
Related Topics
Is It Ok to Use Dashes in Python Files When Trying to Import Them
Pandas: Replace Substring in String
How Find Specific Data Attribute from HTML Tag in Beautifulsoup4
Calling Custom Functions from Python Using Rpy2
Bloomberg Server API and Ruby/Python
Output Seckeycopyexternalrepresentation
How to Apply a Disc Shaped Mask to a Numpy Array
How to Make a Character Jump in Pygame
Convert a Timedelta to Days, Hours and Minutes
Saving Interactive Matplotlib Figures
How to Change an Image Size in Pygame
Beautifulsoup Not Grabbing Dynamic Content
Fama MACbeth Regression in Python (Pandas or Statsmodels)
What's the Ruby Equivalent of Python's Os.Walk
Swift Playground Error: Module 'Python' Has No Member Named 'Import'
Matplotlib: Annotating a 3D Scatter Plot
What Is the Good Python3 Equivalent for Auto Tuple Unpacking in Lambda