14

Python 2 documentation says that super() function "returns a proxy object that delegates method calls to a parent or sibling class of type."

The questions:

  1. What is a sibling class in Python?
  2. How do you delegate a method call to a sibling class?

My presumption was that a sibling for a given class is a class that inherits from the same parent. I drafted the following code to see how a method call can be delegated to a sibling, but it didn't work. What do I do or understand wrong?

class ClassA(object):
    def MethodA(self):
        print "MethodA of ClassA"

class ClassB(ClassA):
    def MethodB(self):
        print "MethodB of ClassB"

class ClassC(ClassA):
    def MethodA(self):
        super(ClassC, self).MethodA()

    def MethodB(self):
        super(ClassC, self).MethodB()

if __name__ == '__main__':
    ClassC().MethodA() # Works as expected

    # Fail while trying to delegate method to a sibling.
    ClassC().MethodB() # AttirbuteError: 'super' object has no attribute 'MethodB'
golem
  • 1,820
  • 1
  • 20
  • 25
  • I don't have time to write a full answer, but I think the docs are referring to multiple inheritance. If class C inherits from both A and B then you could consider A and B to be 'siblings' in some sense. `super()` calls in the body of A on an instance of C will delegate to B. See [this question](http://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance) for the low-down. – Benjamin Hodgson Jan 15 '15 at 00:10
  • 1
    The accepted answer to this question here explains the details of how `super()` can indeed call parent but also sibling classes: http://stackoverflow.com/questions/5033903/python-super-method-and-calling-alternatives – Simeon Visser Jan 15 '15 at 00:11
  • @SimeonVisser, I've seen that question, even starred it. It doesn't give a clear answer to what a sibling class is. This question is related to that one, but clearly isn't a duplicate. – golem Jan 15 '15 at 00:14
  • @golem: well, in that example you can see a case where `super()` calls a sibling class instead of the parent class (both `B` and `C` are children of `A`). A sibling class indeed means what you think it means: two classes that have the same parent class and thus they're siblings. `super()` can call parent classes as well as sibling classes. Hope this helps - if not please let me know. – Simeon Visser Jan 15 '15 at 09:31
  • @SimeonVisser, please unmark the question as duplicate so I can move my answer from the question body to an answer. Thank you. – golem Jan 15 '15 at 16:32

2 Answers2

14

After some further research and reading Python’s super() considered super! article I came to the following conclusions:

  1. A sibling class is what I was thinking it was. It is a class that inherits from the same parent. It is the definition of the Python documentation that threw me off the course. It seems that when Python documentation says delegates method calls to a parent or sibling class it means to a parent or parent's sibling which is also a base class of a given child. That is a diamond inheritance must take place.

  2. super() function delegates a method call to a parent's sibling class automatically based on MRO (method resolution order).

Here's an interesting case I found while experimenting with super() function:

class Class0(object):
    def MethodA(self):
        print("MethodA of Class0")

class ClassA(Class0):
    def MethodA(self):
        super(ClassA, self).MethodA()
        print("MethodA of ClassA")

class ClassB(Class0):
    def MethodA(self):
        print("MethodA of ClassB")

class ClassC(ClassA, ClassB):
    def MethodA(self):
        super(ClassC, self).MethodA()

if __name__ == '__main__':
    ClassC().MethodA()

The code will print

MethodA of ClassB
MethodA of ClassA

If you as me wonder why MethodA of Class0 never gets printed, here is the explanation as I understand it. The MRO of the ClassC as printed with print(ClassC.__mro__) is

(<class '__main__.ClassC'>, <class '__main__.ClassA'>, <class '__main__.ClassB'>, <class '__main__.Class0'>, <class 'object'>).

Now if you follow the MRO the super() function of MethodA() of ClassC will call the MethodA() of ClassA, which before printing will call MethodA() of classB (since it's next in MRO). And the MethodA() of ClassB in its turn will just print and exit since it doesn't use super() function to delegate the method call further up the MRO chain.

golem
  • 1,820
  • 1
  • 20
  • 25
  • So `ClassA` and `ClassB` are considered "sibling" classes because they both inherit from `Class0`? I'm having trouble understanding why `ClassB` comes **after** `ClassA` when the MRO is printed. – Jarad Feb 14 '20 at 00:16
  • I was exceptionally confused by this MRO, because the Python documentation for Classes says that MRO is "depth-first, left-to-right" - this implies "MethodA of Class0" should be printed first. Turns out Python has another rule which basically says an ancestor class (eg Class0) cannot be chosen to resolve a method call if there is a descendant of Class0 later in the MRO that could resolve the call. This is called the "good head" rule and ensures that the most specific (re-)definition of the method takes precedence. – David M Apr 21 '20 at 19:07
5

A sibling is a class with the same parent, as you suspected. The case you're missing is that super could call a sibling method if this class itself is being multiply inherited from:

class A(object):
  def something(self):
    print("A")
class B(A):
  def something(self):
    print("B")
class C(A):
  def something(self):
    print("C, about to call super()")
    super(C, self).something()
class D(C, B):
  def something(self):
    super(D, self).something()

>>> D().something()
C, about to call super()
B

In C, we called super(), but we got B - which is a sibling, not a parent and not the parent of a sibling, but an actual direct sibling of C.

lmm
  • 17,386
  • 3
  • 26
  • 37
  • Thanks, I've come to the same conclusion. I think the more precise definition would be *parent's sibling which is also a base class of a given child* - the definition I used in my own answer. – golem Jan 15 '15 at 17:19
  • No, that's not an accurate definition. `B` is not a *parent's sibling* of `C`. It's a *sibling* of `C`. – lmm Jan 15 '15 at 17:20
  • B is a first parent, C is a second parent. B and C are siblings. In *parent's sibling which is also a base class of a given child* substitute and you'll get: B's sibling which is also a base class of D. That is C =). – golem Jan 15 '15 at 17:24
  • 1
    `B` is not a parent *in `C`*, which is what's calling `super` and getting `B`. – lmm Jan 15 '15 at 22:19