preface

This article is intended to be popular. It is not recommended to use multiple inheritance in real programming, or to use multiple inheritance sparingly, to avoid making the code difficult to understand.

Method parsing order (MRO)

What is important about multiple inheritance is its method resolution order (which can be understood as the search order of the class), the MRO. This depends on whether the class is a new class or a classic class, because the search algorithms are different.

In Python2 and before, classes derived from any built-in type (as long as a built-in type is located somewhere in the class tree) are new classes; Conversely, classes that are not derived from any of the built-in types are called classical classes

After Python3, without this distinction, all classes derive from the built-in type object, whether or not they explicitly inherit object, and are new classes.

For classical classes, MRO of multiple inheritance is depth-first, that is, search from bottom up; The MRO of the new class adopts the C3 algorithm (in different cases, it can be shown as breadth first or depth first).

Examples of the C3 algorithm behaving as depth-first:

# C3- depth first (D -> B -> A -> C)
class A:
    var = 'A var'


class B(A):
    pass


class C:
    var = 'C var'


class D(B, C):
    pass


if __name__ == '__main__':
    # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>, <class 'object'>]
    print(D.mro())
    # A var
    print(D.var)
Copy the code

Examples of C3 algorithms behaving as breadth-first:

# c3-width priority (D -> B -> C -> A)
class A:
    var = 'A var'


class B(A):
    pass


class C(A):
    var = 'C var'


class D(B, C):
    pass


if __name__ == '__main__':
    # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
    print(D.mro())
    # C var
    print(D.var)

Copy the code

Note: The detailed algorithm for C3 is not discussed in this article, because I don’t understand it either.

Diamond multiple inheritance

Here is an example of rhomboid multiple inheritance: the C3 algorithm behaves as breadth-first.

One problem with diamond multiple inheritance is that A is initialized twice, as follows:

class A:
    def say(self):
        print("A say")


class B(A):
    def say(self):
        print("B say")
        A.say(self)


class C(A):
    def say(self):
        print("C say")
        A.say(self)


class D(B, C):
    def say(self):
        print("D say")
        B.say(self)
        C.say(self)


if __name__ == '__main__':
    dd = D()
    dd.say()

Copy the code

If you only want to call A once, use the super method:

class A:
    def say(self):
        print("A say")


class B(A):
    def say(self):
        print("B say")
        super().say()


class C(A):
    def say(self):
        print("C say")
        super().say()


class D(B, C):
    def say(self):
        print("D say")
        super().say()


if __name__ == '__main__':
    print(D.mro())
    dd = D()
    dd.say()

Copy the code

_init_ and super ()

1. If the parent class has an init method and the child class does not, the child class inherits the parent class’s init method by default

class A:
    def __init__(self, a1, a2):
        self.a1 = a1
        self.a2 = a2

    def say(self):
        print("A say, a1: %s, a2: %s" % (self.a1, self.a2))


class B(A):
    def say(self):
        print("B say, a1: %s, a2: %s" % (self.a1, self.a2))


if __name__ == '__main__':
    B inherits A's init method, so it also passes a1, a2 parameters
    bb = B("10"."100")
    bb.say()

Copy the code

2. If the parent class has an init method and a subclass has an init method, the subclass overrides the parent class’s init method

class A:
    def __init__(self, a1, a2):
        self.a1 = a1
        self.a2 = a2

    def say(self):
        print("A say, a1: %s, a2: %s" % (self.a1, self.a2))


class B(A):
    def __init__(self, b1):
        self.b1 = b1

    def say(self):
        print("B say, b1: %s" % self.b1)


if __name__ == '__main__':
    B overwrites A's init method, so it only needs to pass in b1 and does not have a1, a2 attributes
    bb = B("10")
    bb.say()

Copy the code

3. For the second point, in order to use or extend the behavior of the parent class, it is more common to override the init method while showing that the parent class’s init method is called (meaning that the parameters passed are reduced to three).

# three ways to write
class A:
    def __init__(self, a1, a2):
        self.a1 = a1
        self.a2 = a2

    def say(self):
        print("A say, a1: %s, a2: %s" % (self.a1, self.a2))


class B(A):
    def __init__(self, b1, a1, a2):
        # 1: Python2
        # super(B, self).__init__(a1, a2)
        # Second (recommended) : Python3, equivalent to the first
        super().__init__(a1, a2)
        This is equivalent to the first two methods, except that this method requires the display to call the base class, whereas the second method does not
        # A.__init__(self, a1, a2)
        self.b1 = b1

    def say(self):
        print("B say, a1: %s, a2: %s, b1: %s" % (self.a1, self.a2, self.b1))


if __name__ == '__main__':
    B overwrites A's init method, so it only needs to pass in b1 and does not have a1, a2 attributes
    bb = B("10"."100"."1000")
    bb.say()
Copy the code

A final note

Be careful not to write the __init__ method incorrectly. Avoid writing __ini__ or anything else, because the two are too similar to each other.

reference

Beware of falling into the Python multiple inheritance trap