1

I have a class that store a numpy array and the class has a method to modify the array

class A:
    def __init__(self, matrix):
        self.Matrix = matrix
    def modify(self):
         self.Matrix *= 2

obj = A(someMatrix)

Now here's the ridiculous part

p = obj.Matrix
obj.modify()
q = obj.Matrix

p - q  # this is the 0 matrix !!!

So I realized p and q must be storing the same address (to obj.Matrix) but this is clearly not what I am trying to do: I wanted to take the difference between the matrix before and after it's been updated!

Then I did this

p = np.array(obj.Matrix)
obj.modify()
q = np.array(obj.Matrix)

p - q  # worked 

But if I do

class A:
    def __init__(self):
        self.k = 1
    def mod(self):
        self.k *= 2

a = A()

p = a.k
a.mod()
q = a.k

print(p,q) # p = 1, q = 2  

But how do I solve this problem in general: how do I store the value of the object variable/attribute rather than the address to the variable??

Thank you a ton!

zmkm
  • 117
  • 1
  • 4

1 Answers1

0

Code-specific answer:

p and q point to the same object because self.Matrix *= 2 is an inplace-operation that mutates self.Matrix. (Under the hood, self.Matrix.__imul__ is called.) You could change the line to self.Matrix = self.Matrix * 2, which creates a new matrix and then rebinds self.Matrix. (Under the hood, self.Matrix.__mul__ is called.)

General answer:

Variables/names/identifiers (same thing) are just labels for objects. You need the correct mental model to avoid surprises, I suggest that you start with this excellent talk by Ned Batchelder.

Whenever you do

a = some_object
b = a
a.mutate()

the mutation will be seen across all names, because there has only ever been one object in memory. Assignment NEVER copies data. You need to copy the data explicitly, for example through a copy method the object in question might have.

timgeb
  • 76,762
  • 20
  • 123
  • 145
  • I edited the question to include another case, how come this works but the numpy case doesn't? – zmkm Apr 09 '22 at 22:01
  • @zmkm because integers don't have `__imul__`, so `*=` falls back to `__mul__`, which creates a new integer and then rebinds the name `self.k`. – timgeb Apr 09 '22 at 22:04
  • @zmkm and before you ask, yes you have to memorize which data types are mutable and which are not. For builtin mutable types, augmented assignments such as `*=`, `+=`, ... work in-place. (Integers are not mutable.) – timgeb Apr 09 '22 at 22:05