Home > Net >  Why did my code refer to Class B and not Class C?
Why did my code refer to Class B and not Class C?

Time:01-26

Here is my code.

class A(object):
    def __init__(self):
        self.a = 1
    def x(self):
        print("A.x")
    def y(self):
        print("A.y")
    def z(self):
        print("A.z")

class B(A):
    def __init__(self):
        A.__init__(self)
        self.a = 2
        self.b = 3
    def y(self):
        print("B.y")
    def z(self):
        print("B.z")

class C(object):
    def __init__(self):
        self.a = 4
        self.c = 5
    def y(self):
        print("C.y")
    def z(self):
        print("C.z")

class D(C, B):
    def __init__(self):
        C.__init__(self)
        B.__init__(self)
        self.d = 6
    def z(self):
        print("D.z")

obj = D()

print(obj.a)

Why does print(obj.a) return 2 and not 4? I thought Python scans inputs from left to right. So with that logic it should refer to the superclass C and find that self.a = 4 and not refer to the superclass B where self.a = 2

CodePudding user response:

The attribute obj.a is found directly in the instance namespace, so the MRO is not really involved here.

>>> print(obj.__dict__)
{'a': 2, 'c': 5, 'b': 3, 'd': 6}

If you're asking why the instance namespace contains a=2 and not a=4, it's because it was set to 4 initially and then overwritten:

C.__init__(self)  # sets self.__dict__["a"] = 4
B.__init__(self)  # sets self.__dict__["a"] = 2

CodePudding user response:

Why does print(obj.a) return 2 and not 4?

Because the object obj can only have one attribute named a, and its value was most recently set to 2.

I thought Python scans inputs from left to right.

To determine the class' method resolution order, yes. However, the MRO is only relevant when either implicitly looking for attributes that are missing in the current class, or explicitly passing along the chain via super.

So with that logic it should refer to the superclass C

No; when obj.a is looked up at the end, it doesn't look in any classes at all for the attribute, because the object contains the attribute. It doesn't look in C, B or A. It looks in obj, finds the attribute, and stops looking. (It does first look at D, in case it defines some magic that would override the normal process.)

The base classes do not create separate namespaces for attributes. Rather, they are separate objects, whose attributes can be found by the attribute lookup process (and, when they are, those attributes might be automatically converted via the descriptor protocol: e.g. attributes that are functions within the class, will normally become methods when looked up from the instance).

But when e.g. self.a = 2 happens, self means the same object inside that code that obj means outside. Assigning an attribute doesn't do any lookup - there's nothing to look up; there's already a perfectly suitable place to attach the attribute. So it just gets attached there. Where it will subsequently be found.

Because the parent classes were initialized explicitly, the order is clear: D.__init__ calls C.__init__ which sets self.a = 4; then that returns and D.__init__ also calls B.__init__; that calls A.__init__, which sets self.a = 1; then B.__init__ directly sets self.a = 2; then all the calls return (after setting other attributes). In each case, self is naming the same object, so it sets the same attribute in the same namespace (i.e. the attributes of that object, treated as a namespace).

and not refer to the superclass B where self.a = 2

Again, they are not separate namespaces (and unlike some other languages, not separate "parts" of the object), so B isn't a "place where" self.a can have a different value from the one it has "in" C. There's only one self object, with one __dict__, and one a (equivalently, __dict__['a']).

  • Related