Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 681
user1956609 Avatar asked Sep 11 '15 15:09

user1956609


People also ask

Can class attributes be changed?

But be careful, if you want to change a class attribute, you have to do it with the notation ClassName. AttributeName. Otherwise, you will create a new instance variable.

Can you edit a class in Python?

Built-in classes can't be modified, but you can "hide" a built-in class (or any other of course) by one of the same name.


2 Answers

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
like image 76
jonrsharpe Avatar answered Sep 30 '22 05:09

jonrsharpe


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.

like image 33
Daniel Roseman Avatar answered Sep 30 '22 06:09

Daniel Roseman