1

As I understand it, if all classes use the "new style" then this would happen:

class A(object):
    def __init__(self):
        print("Entering A")
        super().__init__()
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super().__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        super().__init__()
        print("Leaving C")

c = C()
Entering C
Entering A
Entering B
Leaving B
Leaving A
Leaving C

After understanding that super uses the MRO of the original object (c) it makes sense why the Entering/Leaving are the way they are. But what if I need to pass some specific arguments to A and B, but also the order in which the arguments are received in C is not similar to A and B:

class A(object):
    def __init__(self, x1, x2):
        print(f"Entering A [x1={x1}, x2={x2}]")
        super().__init__()
        print("Leaving A")

class B(object):
    def __init__(self, y1, y2):
        print(f"Entering B [y1={y1}, y2={y2}]")
        super().__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self, x1, y1, x2, y2):
        print(f"Entering C [x1={x1}, y1={y1}, x2={x2}, y2={y2}]")
        super().__init__()
        print("Leaving C")

c = C('x1', 'y1', 'x2', 'y2')

I can think of just passing kwargs around all day but I feel like there might be a nicer way.

Also, what if B is part of a library so we can't change it and it uses positional arguments, not kwargs? In that case what can I do since I don't think it's a good idea to manually mess with the MRO but at the same time the constructor of B won't take the kwargs?

What is the correct way of resolving the above issue?

4
  • I am perplexed, you would want to call parent's constructor without knowing which parent and what signature the constructor has up front? You could do that with kwargs and get the bits you need if you need the flexibility, but "the correct" way would be to call A.__init__(self, x1, x2) and B.__init__(self, y1, y2). No magic, no ambiguity. Commented Aug 31, 2020 at 12:45
  • @OndrejK.but isnt that the "old style"? I understnad how you can use that style but the implication is that the "new style" should be able to do everything the "old style" can and provide some extra benefits on top of that (in this case more robust code for factorization). Commented Aug 31, 2020 at 14:45
  • For what you seem to be describing, it would not be matter of "old style", but being explicit about the design / intentions. Commented Aug 31, 2020 at 15:24
  • @OndrejK.can you then explain why this "new style" was introduced if it is not capable of performing the same tasks as the "old style"? Commented Aug 31, 2020 at 17:41

1 Answer 1

2

OK, this won't really fit into comments any more, but there seems to be a bit of misunderstanding at play here.

For:

class C:
    def __init__(self, arg1=1):
        self.attr1 = arg1

class D(C):
    def __init__(self, arg1):
        super().__init__()

This would then yield:

d = D(2)
print(d.attr1)  # prints: 1

Because through super().__init__() we called parent's constructor, but we did not pass any argument and it ran with its default value.

Or to quote the docs for what super does:

Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class.

I.e. you "only" get (possibly bound) resolved method to call. Not more, not less. It does not sort out any arguments you pass around. So your example would actually not work, because both parents' constructors expect an instance and two more arguments.

In your example you could say:

class A:
    def __init__(self, x1, x2):
        print(f"Entering A [x1={x1}, x2={x2}]")
        print("Leaving A")

class B:
    def __init__(self, y1, y2):
        print(f"Entering B [y1={y1}, y2={y2}]")
        print("Leaving B")

class C(A, B):
    def __init__(self, x1, y1, x2, y2):
        print(f"Entering C [x1={x1}, y1={y1}, x2={x2}, y2={y2}]")
        A.__init__(self, x1, x2)
        B.__init__(self, y1, y2)
        print("Leaving C")

Because you call each specific parent constructors with corresponding specific arguments. The documentation (same place, few paragraph down on second common use case with multiple inheritance) also contains hint regarding that:

Good design dictates that this method have the same calling signature in every case (because the order of calls is determined at runtime, because that order adapts to changes in the class hierarchy, and because that order can include sibling classes that are unknown prior to runtime).

That is, you get a proxy to call the method... and you call it using its arguments... which has to be a fixed / stable set as you cannot be sure up front which method are you reaching through that proxy.

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.