You are right - in the case of Foo.a access to self.a really refers to Foo.a , which is shared between all instances of Foo . However, when you update self.n with += , you actually create an instance level variable on self that obscures Foo.n :
>>> import dis >>> dis.dis(Foo.bar) 5 0 LOAD_FAST 0 (self) 3 LOAD_ATTR 0 (a) 6 LOAD_ATTR 1 (append) 9 LOAD_CONST 1 ('foo') 12 CALL_FUNCTION 1 15 POP_TOP 6 16 LOAD_FAST 0 (self) 19 DUP_TOP 20 LOAD_ATTR 2 (n) 23 LOAD_CONST 2 (1) 26 INPLACE_ADD 27 ROT_TWO 28 STORE_ATTR 2 (n) 31 LOAD_CONST 0 (None) 34 RETURN_VALUE
In other words, when you execute self.a.append('some value') , the interpreter extracts a from memory through the name in Foo , and then mutates the list pointed to by Foo.a
On the other hand, when you execute the self.n += 1 interpreter:
- Selects
n from Foo (because it cannot find n on self ) - Creates a new value
n + 1 - Saves the new value in the
n attribute to self
source share