2

I have a child class named USA and it has two parent classes, A and B. Both parents have a method named change_to_4 and in B.__init__ I call the method, but instead of using the method that I defined in B it uses the A definition of the change_to_4 method.

class A:
    def __init__(self) -> None:
        self.a = 1
        super().__init__()

    def change_to_4(self):
        self.x = 4


class B:
    def __init__(self) -> None:
        self.b = 2
        self.change_to_4()
        super().__init__()

    def change_to_4(self):
        self.b = 4


class USA(A, B):
    def __init__(self) -> None:
        super().__init__()


print(f"A vars = {vars(A())}")
print(f"B vars = {vars(B())}")
print(f"USA vars = {vars(USA())}")

print(f"USA mro -> {USA.__mro__}")

I expect something like this:

A vars = {'a': 1}
B vars = {'b': 4}
USA vars = {'a': 1, 'b': 4}
USA mro -> (<class '__main__.USA'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

But the output is

A vars = {'a': 1}
B vars = {'b': 4}
USA vars = {'a': 1, 'b': 2, 'x': 4}
USA mro -> (<class '__main__.USA'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
5
  • 1
    Add a print(self) in B's __init__.py; it might clear things up a bit (hint: self is perhaps not what you think it is). Commented Jan 18, 2024 at 8:59
  • @9769953 self of the USA was <__main__.USA object at 0x7f94bf6c79a0> I expected it to be different from B, but my main issue is I expect when I call USA its attributes being made from its parent and be exactly them, but this is not the case. Commented Jan 18, 2024 at 9:11
  • @blhsing yes exactly, I didn't call the A.change_to_4 at all but in the result i see 'x':4 that is what A.change_to_4 does despite never calling it. Commented Jan 18, 2024 at 9:16
  • Oops my bad for misreading your code. Commented Jan 18, 2024 at 9:18
  • 1
    @mkrieger1 oh I wrote it wrong, I meant "but instead of using the method that I defined in B it uses the A definition of the `change_to_4" method. Commented Jan 18, 2024 at 10:07

1 Answer 1

3

When the interpreter looks up an attribute of an instance, in this case self.change_to_4 on an instance of USA, it first tries to find 'change_to_4' in self.__dict__, and failing to find it there, the interpreter then follows the method resolution order of USA (USA, A, B, object as shown in the output) to find the change_to_4 attribute in the first base class that has it defined.

Excerpt from the documentation of Custom classes:

Class attribute references are translated to lookups in this dictionary, e.g., C.x is translated to C.__dict__["x"] (although there are a number of hooks which allow for other means of locating attributes). When the attribute name is not found there, the attribute search continues in the base classes. This search of the base classes uses the C3 method resolution order which behaves correctly even in the presence of ‘diamond’ inheritance structures where there are multiple inheritance paths leading back to a common ancestor.

In this case, A is the first base class of USA that defines change_to_4, so self.change_to_4() gets translated to A.change_to_4(self), resulting in self.x = 4 getting executed.

That the call self.change_to_4() is made from a method in B does not change the fact that A comes before B in the method resolution order of a USA instance.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.