1

I want to call a class attribute and append it to a list. Here is a simple script:

class class_1():
    def __init__(self):
        self.x = np.array([0, 0])

    def update(self):
        self.x += 1
        return self.x

cl_1 = class_1()
a = []
for i in range(3):
    y = cl_1.update()
    print(y)
    a.append(y)
print(a)  
# output:
[1 1]
[2 2]
[3 3]
[array([3, 3]), array([3, 3]), array([3, 3])]

but I expect [array([1, 1]), array([2, 2]), array([3, 3])] as the final value of list a. I checked that there is no problem with python numbers:

class class_2():
    def __init__(self):
        self.x = 0

    def update(self):
        self.x += 1
        return self.x

cl_2 = class_2()
a = []
for i in range(3):
    y = cl_2.update()
    print(y)
    a.append(y)
print(a)
#output
1
2
3
[1, 2, 3]     
9
  • This is a duplicate that has been asked a million times over (no reflection on you OP), but I can't seem to find a good hammer candidate. Commented Aug 2, 2018 at 21:08
  • 1
    Basically, notice that you are always appending a reference to the same array. The whole point of += is that it modifies in place, instead of creating a copy. Commented Aug 2, 2018 at 21:13
  • You can use return self.x.copy() to avoid the problem. Commented Aug 2, 2018 at 21:13
  • @DYZ. Or more cleanly, spell out self.x = self.x + 1. Commented Aug 2, 2018 at 21:14
  • @MadPhysicist Fair enough. Depends on the context. Commented Aug 2, 2018 at 21:17

1 Answer 1

1

Unlike Python integers, numpy arrays really allow in-place modification. With a Python integer, which is immutable, x += 1 and x = x + 1 have the same result. In both cases, x is rebound to a new integer object.

When you do self.x += 1 to a numpy array, you do not change the reference that self.x points to. A new array is not allocated, but instead, every element in the internal buffer of the existing array is incremented. Note that an assignment still happens, but with the same reference as before.

To simulate the behavior of an integer in this context, explicitly write out the operation you want:

self.x = self.x + 1

In this case self.x + 1 is an entirely new array, which can then be reassigned to self.x as an integer would be.

You have two options when it comes to how you want to fix your code:

  1. If you are OK with creating a new array every time, and discarding the previous array, change the update method as shown above. In some ways this solution is cleanest because it minimizes the effects on outside references. At the same time, you might not want to create a new copy every time.

  2. If you want to avoid creating unnecessary copies in most cases (or at least have control over when the copies get made), use @DYZ's suggestion. Instead of a.append(y), do

    a.append(y.copy())
    

    or alternatively, make the copy immediately:

    y = cl_2.update().copy()
    

TL;DR

One of the most common beginner pitfalls is happening here: creating a list from references to the same mutable object, and then mutating the object. All the references will end up with the last value of the object, as expected.

Sign up to request clarification or add additional context in comments.

Comments

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.