Множественное наследование в Python
Язык программирования 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, который будет уметь и плавать и ездить.
Обратите внимание, что инстанс класса 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()