Why attribute (type list) in class reference old id?

class x:
    i = 0
    y = []

    def __init__(self):
        self.y.append('x')
        self.i = self.i + 1
        print('id y :', id(self.y))
        print('id i :', id(self.i))

    def print(self):
        print(self.y)
        print(self.i)


x().print()
x().print()
x().print()

results

id y : 140196860620608
id i : 140197396277552
['x']
1
id y : 140196860620608
id i : 140197396277552
['x', 'x']
1
id y : 140196860620608
id i : 140197396277552
['x', 'x', 'x']
1

I found the answer.

When accessing member attributes via self, Python first looks for the attribute in the instance itself. If it does not find it, it looks at the class. So,

self.y.append('x')

will look for a y attribute with an append method on the current instance, and when it doesn’t find it, look for it on the class. Since x.y exists and has an append method, that’s what end up being used.

On the other hand

self.i = self.i + 1

will look for i on the current instance, and when it doesn’t find it it will create it, just like you can do this:

class A:
    pass

a = A()
a.xyz = 1000  # create instance variable xyz
print(a.xyz)  # prints 1000

To avoid this, you should refer to class variables via the class name, not via self:

A.i = A.i + 1

You can also use __slots__ to prevent the creation of unintentional instance variables:

class A:
    __slots__ = ["a"]

a = A()
a.a = 1
a.b = 1  # AttributeError: 'A' object has no attribute 'b'