Yes, it's because one is immutable and one isn't. Or rather, it's that you are mutating one and not the other. (What matters isn't whether the object "is mutable", what matters is whether you actually mutate it.) It's also because you're using class variables instead of instance variables (see this question).
In your class definition, you create three class variables, shared among all instances of the class. After creating an instance of your class, if you do self.x
on that instance, it will not find x
as an attribute on the instance, but will look it up on the class. Likewise self.z
will look up on the class and find the one on the class. Note that because you made z
a class variable, there is only one list that is shared among all instances of the class (including all instances of all subclasses, unless they override z
).
When you do self.y = self.x
, you create a new attribute, an instance attribute, on only the instance.
However, when you do self.z.append(...)
, you do not create a new instance variable. Rather, self.z
looks up the list stored on the class, and then append
mutates that list. There is no "overwriting". There is only one list, and when you do the append you are changing its contents. (Only one item is appended, because you have if not self.z
, so after you append one, that is false and subsequent calls do not append anything more.)
The upshot is that reading the value of an attribute is not the same as assigning to it. When you read the value of self.x
, you may be retrieving a value that is stored on the class and shared among all instances. However, if you assign a value to self.x
, you are always assigning to an instance attribute; if there is already a class attribute with the same name, your instance attribute will hide that.