Python class variable list vs int inconsistent behavior

Viewed 137

While working on a piece of code today I've found this somewhat surprising behavior and I'm looking for an explanation. See the following piece of code and output it produces

class Test:
    t1 = []
    t2 = 1

    def xyz(self):
        self.t1 += [1]
        self.t2 += 1


for i in range(10):
    v = Test()
    v.xyz()
    print(v.t1)
    print(v.t2)

Output

[1]
2
[1, 1]
2
[1, 1, 1]
2
[1, 1, 1, 1]
2
[1, 1, 1, 1, 1]
2
[1, 1, 1, 1, 1, 1]
2
[1, 1, 1, 1, 1, 1, 1]
2
[1, 1, 1, 1, 1, 1, 1, 1]
2
[1, 1, 1, 1, 1, 1, 1, 1, 1]
2
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
2

My question is why it's somewhat inconsistent? It seems like t2 (int) gets reinitialized every time. On the other hand t1 (list) doesn't. According to documentation I'd assume t2 isn't reinitialized every time and should just be increased. Class properies should be shared among the instances of objects. What's exactly going on here?

1 Answers

self.attr modifies the instance variable attr. You want a classmethod to modify class level attrs:

class MyClass:
    a = 0
    
    @classmethod
    def inc(cls):
        cls.a += 1


for i in range(4):
    c = MyClass()
    c.inc()

print(c.a)
4

Why does the list work, though?

Because the list is a reference to cls.t1, and the in-place addition modifies the reference. This is not the case with ints, where __iadd__ or += still returns a new int:

l1 = []

l2 = l1

l2 += ['HI']

l1
['HI']

a = 2
b = a # b and a point to 2

b += 1 # creates a new int, 3, and assigns to the name b

a # a is the same
2

b
3

The same is happening with your class:

class MyClass:
    v1 = []

    def f(self):
        self.v1 += [1] # self.v1 is another name pointing to the same list as v1 in the class def

So the modification propagates. Using @classmethod modifies cls.v1, which is class MyClass: v1=[]. It's not another name for it, it is the class-level variable v1.

Related