0

This is really surprising for me (and costly in terms of hours spent debugging):

class Test():
    def __init__(self):
        self.init_value = np.array([1, 2])
    
    def func(self):
        test = self.init_value
        test[-1] = 0
        return test
test = Test()
print(test.init_value)
test.func()
print(test.init_value)

My questions are:

  1. Is this because of the properties of numpy arrays (deep copy)?
  2. Is there a way to "fix" object attributes unless I explicitly modify them?
2
  • 1
    This is because you are changing your int_value dynamically. Use test = self.init_value.copy() instead. Commented Aug 31, 2020 at 2:22
  • right, I get that. but it is still surprising for me that object attributes are "dynamically" modifiable depending on the type of the attribute (numpy array in this case). I thought they are not modifiable in default unless I explicitly say self.init_value = new_value. Commented Aug 31, 2020 at 2:34

2 Answers 2

1

To your Question 1): Lets see what happens here first,

self.init_value = np.array([1,2])

a np.array object with the values provided is created in memory, to this the label self.init_value is assigned.

Now, at this step,

test = self.init_value

To the memory location pointed by self.init_value another label test is assigned. you can test this by modifying the func() as follows:

def func():
    test = self.init_value

    # Both these values will be same.
    print(hex(id(test)))
    print(hex(id(self.init_value)))

    test[-1] = 0
    return test

So when you do test[-1] = 0 you are basically accessing the last element of the object pointed by test and changing it to value 0, since both self.init_value and test point to the same object, it gets modified.

But if you reassign some other object with the same label name, the label gets removed from the previous object and gets attached to this new object. You can verify this by checking the memory location using the code:

hex(id(your_label))

To your Question 2:

If you don't want test to modify self.init_value, modify the code to use deepcopy() as follows:

import copy

test = copy.deepcopy(self.init_value)
Sign up to request clarification or add additional context in comments.

Comments

1

This is one of those weird reference vs. value issues.

Because self is a reference, your test variable is a reference to self.init_value...

So you ARE modifying the attribute. @matfux’s solution has your variable store a copy of self.init_value rather than a reference to it.

Honestly, a tricky bit in most OOP situations.

1 Comment

yeah, as you said, the tricky thing for me is that "self is a reference". I was aware of dynamic modification in situations that b = a (a is a numpy array), then changes in b will modify a. I just never thought the same thing applies here (again, because I didn't realize that "self is a reference")

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.