Язык программирования Python являясь языком, поддерживающим парадигму объектно-ориентированного программирования (ООП), также поддерживает и возможность множественного наследования. То есть, возможность у класса потомка наследовать функционал не от одного, а от нескольких родителей. Благодаря этому мы можем создавать сложные структуры, сохраняя простой и легко-поддерживаемый код.
Например, у нас есть класс автомобиля:

class Auto:
    def ride(self):
        print("Riding on a ground")

Так же у нас есть класс для лодки:

class Boat:
    def swim(self):
        print("Sailing in the ocean")

Теперь, если нам нужно запрограммировать автомобиль-амфибию, который будет плавать в воде и ездить по земле, мы вместо написания нового класса, можем просто унаследовать от уже существующих:

class Amphibian(Auto, Boat):
    pass

a = Amphibian()
a.ride()
a.swim()

Теперь мы можем создать инстанс класса Amphibian, который будет уметь и плавать и ездить.

Python множественное наследование

Обратите внимание, что инстанс класса Amphibian, будет одновременно инстансом класса Auto и Boat, то есть:

>>> a = Amphibian()
>>> isinstance(a, Auto)
True
>>> isinstance(a, Boat)
True
>>> isinstance(a, Amphibian)
True

Примеси (Mixins) в Python

Использование множественного наследования, позволяет нам создавать, так называемые, классы-примеси или миксины. Представим, что мы программируем класс для автомобиля. Мы хотим, чтобы у нас была возможность слушать музыку в машине. Конечно, можно просто добавить метод play_music() в класс Car:

class Car:
    def ride(self):
        print("Riding a car")

    def play_music(self, song):
        print("Now playing: {} ".format(song))

>>> c = Car()
>>> c.ride()
Riding a car
>>> c.play_music("Queen - Bohemian Rhapsody")
Now playing: Queen - Bohemian Rhapsody

Но что если, у нас есть еще и телефон, радио или любой другой девайс, с которого мы будем слушать музыку. В таком случае, лучше вынести функционал проигрывания музыки в отдельный класс-миксин:

class MusicPlayerMixin:
    def play_music(self, song):
        print("Now playing: {}".format(song))

Мы можем "домешивать" этот класс в любой, где нужна функция проигрывания музыки:

class Smartphone(MusicPlayerMixin):
    pass


class Radio(MusicPlayerMixin):
    pass


class Amphibian(Auto, Boat, MusicPlayerMixin):
   pass 

Порядок разрешения методов (Method Resolution Order / MRO) в Python. Ромбовидное наследование (The Diamond Problem)

Итак, классы-наследники могут использовать родительские методы. Но что, если у нескольких родителей будут одинаковые методы? Какой метод в таком случае будет использовать наследник? Рассмотрим классический пример:

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

class B(A):
    def hi(self):
        print("B")

class C(A):
    def hi(self):
        print("C")

class D(B, C):
    pass

d = D()
d.hi()

Эта ситуация, так называемое ромбовидное наследование (diamond problem) решается в Python путем установления порядка разрешения методов. В Python3 для определения порядка используется алгоритм поиска в ширину, то есть сначала интерпретатор будет искать метод hi в классе B, если его там нету - в классе С, потом A. В Python второй версии используется алгоритм поиска в глубину, то есть в данном случае - сначала B, потом - А, потом С. В Python3 можно посмотреть в каком порядке будут проинспектированы родительские классы при помощи метода класса mro():

>>> D.mro()
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

Если вам необходимо использовать метод конкретного родителя, например hi() класса С, нужно напрямую вызвать его по имени класса, передав self в качестве аргумента:

class D(B, C):
    def call_hi(self):
        C.hi(self)

d = D()
d.call_hi()