Changing a class attribute within __init__

北城余情 提交于 2020-05-29 04:19:30

问题


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?


回答1:


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.




回答2:


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



回答3:


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


来源:https://stackoverflow.com/questions/32527535/changing-a-class-attribute-within-init

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!