Today, I read the official doc of super.
In which it mentioned multiple inheritance will be decided by the __mro__ attribute of a class.
So I did a bit
Look at the __mro__ of Son:
__main__.Son, __main__.Father, __main__.GrandFather, __main__.Mother, object
According to the doc:
The
__mro__attribute of the type lists the method resolution search order
So methods will be searched according to the order in the __mro__ list, from left to right. Call of super(type, instance) will change the starting position to the type specified as the first argument of super() in the __mro__ list of the class of the instance specified as the second argument (if the second argument passed to super is a instance):
super(Son, s) will proxy to __main__.Father
super(Father, s) will proxy to __main__.GrandFather
super(GrandFather, s) will proxy to __main__.Mother
super(Mother, s) will proxy to object
The interesting part is why __mro__ of Son is like it is. In other words why Mother is after GrandFather. This is because of how the linearization is working in python:
the linearization of C is the sum of C plus the merge of the linearizations of the parents and the list of the parents.
See the examples in the documentation you mentioned, it explains a very similar case.
So that final result is actually correct: super(GrandFather, s).p() should be I'm female.
From chapt 32 of learning python:
each super call selects the method from a next class following it in the MRO ordering of the class of the self subject of a method call.
so for super(cls, instance)(isinstance(instance, cls) must be True), the method is selected from the next class in instance.__class__.__mro__ starting from instance.__class__.
for super(cls0, cls1)(issubclass(cls1, cls0) must be True), the method is selected from next class in cls1.__mro__ starting from cls0
in both cases, if the method is not implemented by the next class in the MRO chain, the search will skip ahead until it finds a class with the method defined.