user1956609
user1956609

Reputation: 2202

Changing a class attribute within __init__

I was looking at the Stack Overflow question Counting instances of a class?, and I'm not sure why that solution works and one using simple addition doesn't. I guess this is more of a question of how class vs. instance variables are stored and accessed.

Here's the code I think should work, but instead produces 4 for every id:

class foo():
      num = 3    # trying 3 instead of 0 or 1 to make sure the add is working

      def __init__(self):
        self.num += 1
        self.id = self.num

f = foo()
g = foo()

print f.id    # 4
print g.id    # 4

The self.num +=1 statement is somewhat working (the addition is happening, but not the assignment).

What is happening under the hood that's making this assignment fail here, while the itertools.count assignment succeeds in the other question's solution?

Upvotes: 5

Views: 3117

Answers (3)

jonrsharpe
jonrsharpe

Reputation: 122154

Integers don't implement __iadd__ (in-place add, for +=), as they're immutable. The interpreter falls back to standard assignment and __add__ instead, so the line:

self.num += 1

becomes:

self.num = self.num + 1

On the right-hand side you get foo.num (i.e. 3) via self.num, as you expected, but the interesting thing here is that assigning to the instance attribute num shadows the class attribute. So the line is actually equivalent to:

self.num = foo.num + 1  # instance attribute equals class attribute plus one

All instances end up with self.num == 4 and the class remains foo.num == 3. Instead, I suspect what you wanted is:

foo.num += 1  # explicitly update the class attribute

Alternatively, you could implement it as a @classmethod, working on the class more explicitly:

class Foo():  # note naming convention

    num = 3

    def __init__(self):
        self.increment()
        self.id = self.num  # now you're still accessing the class attribute

    @classmethod
    def increment(cls):
        cls.num += 1

Upvotes: 7

Martijn Pieters
Martijn Pieters

Reputation: 1124788

Augmented assignment does not update the class variable. It does this:

tmp = self.num
self.num = tmp.__iadd__(1)

Note the assignment back to self.num there! As such your class attribute remains untouched. The object.__iadd__() method for integers cannot alter the number in-place because integers are immutable, so foo.num never changes.

You need to explicitly reference the class variable:

foo.num += 1

or

self.__class__.num += 1

Upvotes: 2

Daniel Roseman
Daniel Roseman

Reputation: 600041

self.num += 1 means, basically, 'take the value of self.num, increment it, and assign to self.num.

Looking up an attribute on self will find the class variable if there is no corresponding instance variable. However, assigning will always write to an instance variable. So, this tries to find an instance var, fails, falls back to a class var, gets the value, increments it, then assigns to an instance var.

The reason the answer to the linked question works is because there is no assigning going on; they call next() directly on the class variable, its value is mutated but the name is not reassigned.

Upvotes: 6

Related Questions