3

The code looks like:

class A(object):
    x = 0
    y = 0
    z = []
    def __init__(self):
        super(A, self).__init__()
        if not self.y:
            self.y = self.x
        if not self.z:
            self.z.append(self.x)

class B(A):
    x = 1

class C(A):
    x = 2

print C().y, C().z
print B().y, B().z

The output is

2 [2]
1 [2]

Why is z overwritten but not y? Is it because it's not a immutable type? I looked on python's documentation and didn't find an explanation.

pradyunsg
  • 18,287
  • 11
  • 43
  • 96
Lingkai Kong
  • 33
  • 1
  • 3

3 Answers3

3

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.

Community
  • 1
  • 1
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
0

The issue is that x and y are immutable, while z is mutable, and you mutate it.

self.z.append() does not replace z, it just adds an item to z.

After you run C() in print C().y, C().z (which is creating two different C objects), self.z no longer evaluates to False because it is no longer empty.

If you reverse your two print lines, you'll find the output is

1 [1]
2 [1]
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
0

When Python evaluates the body of class A it instantiates a single list object and assigns it to z. Since the subclasses don't override it, and since list objects are stored by reference in Python, all three classes share the same z list, so the first one you instantiate gets to populate z and then the rest just get whatever was put there. Although you changed the contents of the list, you did not change which list z refers to.

This does not affect y because you're assigning the integer directly into the object's internal dictionary, replacing the previous value.

To fix this, create your array inside the constructor, thus assigning:

class A(object):
    x = 0
    y = 0
    z = None
    def __init__(self):
        super(A, self).__init__()
        if not self.y:
            self.y = self.x
        if not self.z:
            # create a new list
            z = [self.x]

class B(A):
    x = 1

class C(A):
    x = 2

print C().y, C().z
print B().y, B().z
Martin Atkins
  • 62,420
  • 8
  • 120
  • 138