Python множественное наследование порядок

Python множественное наследование порядок

Don’t learn to code. Code to learn!

  • Python — Обзор
  • Основы синтаксиса Python
  • Операторы в Python
  • Типы данных в Python
  • Условные конструкторы в Python
  • Циклы в Python
  • Функции в Python
  • Функциональное программирование в Python
  • ООП в Python
  • Модули в Python
  • Работа с файлами в Python
  • Обработка исключительных ситуаций в 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, который будет уметь и плавать и ездить.

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() :

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

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

Источник

Множественное наследование

Мы продолжаем изучать тему наследования. В языке Python допускается множественное наследование, когда один дочерний класс образуется сразу от нескольких базовых, согласно синтаксису:

class A(base1, base2, …, baseN):

Давайте посмотрим, как это работает и для чего вообще нужно. Признаюсь честно, я не сразу смог придумать учебный пример, где можно было бы показать целесообразность применения множественного наследования. То есть, он применяется не так часто, как обычное наследование от одного класса. Но, тем не менее это тоже важный механизм и некоторые подходы к программированию его активно используют. Например, идея миксинов (mixins) в Python реализуется через множественное наследование. И учебный пример я решил связать именно с ними, а заодно показать еще один прием в программировании.

class Goods: def __init__(self, name, weight, price): print("init MixinLog") self.name = name self.weight = weight self.price = price def print_info(self): print(f", , ")

Пока ничего нового здесь нет. Но потом, к нам подходит тимлид и говорит: — Дорогой сеньор, добавь, пожалуйста, возможность логирования товаров магазина. И как бы вы поступили на месте этого сеньора? Плохой сеньор начнет прописывать логику логирования либо непосредственно в базовом классе Goods, либо уровнем выше (в иерархии наследования). А хороший воспользуется идеей миксинов. Для этого он создаст еще один класс, который можно назвать:

class MixinLog: ID = 0 def __init__(self): print("init MixinLog") self.ID += 1 self.id = self.ID def save_sell_log(self): print(f": товар продан в 00:00 часов")

Этот класс работает совершенно независимо от классов Goods и Notebook и лишь добавляет функционал по логированию товаров с использованием их id. Такие независимые базовые классы и получили название миксинов – примесей. Добавим этот класс в цепочку наследования:

class NoteBook(Goods, MixinLog): pass

И видим ошибку. Очевидно, она связана с тем, что у второго класса MixinLog не был вызван инициализатор. Почему так произошло? Как мы уже знаем, при создании объектов инициализатор ищется сначала в дочернем классе, но так как его там нет, то в первом базовом Goods. Он там есть, выполняется и на этом инициализация нашего объекта NoteBook завершается. Однако, нам нужно также взывать инициализатор и второго базового класса MixinLog. В данном случае, сделать это можно с помощью объекта-посредника super(), которая и делегирует вызов метода __init__ класса MixinLog:

class Goods: def __init__(self, name, weight, price): super().__init__() print("init Goods") self.name = name self.weight = weight self.price = price …

Теперь, после запуска программы, мы видим, что оба инициализатора сработали и ошибок никаких нет. Но откуда функция super() «знает», что нужно обратиться ко второму базовому классу MixinLog, а, скажем, не к базовому классу object, от которого неявно наследуются все классы верхнего уровня? В Python существует специальный алгоритм обхода базовых классов при множественном наследовании. Сокращенно, он называется: MRO – Method Resolution Order И говорит, в каком порядке обходить базовые классы: Мы можем увидеть эту цепочку обхода базовых классов, если распечатать специальную коллекцию __mro__ любого класса:

В консоли появится следующая последовательность: (, , , ) То есть, методы сначала ищутся в самом классе NoteBook, затем, в классе Goods, далее идет класс MixinLog и от него уже идет к классу object. Это цепочка обхода для нашего конкретного примера. При другой иерархии наследования эта цепочка может быть другой, но одно всегда неизменно –первый базовый класс, указанный при наследовании, выбирается первым (после дочернего, разумеется). И это важный момент. Вы всегда можете быть уверены, что инициализатор первого базового класса сработает в первую очередь. Почему это важно? Смотрите, при создании объекта NoteBook мы передаем ему три аргумента. Эти три аргумента, затем, передаются в инициализатор. И так как первым будет вызван инициализатор класса Goods, то мы уверены, что эти аргументы будут переданы именно в него, а не в какой-то другой инициализатор других базовых классов. И какая бы цепочка наследования у нас ни была, все равно первым будет вызываться метод __init__ класса Goods, потому что он записан первым. Это гарантирует работоспособность нашей программы при разных иерархиях множественного наследования. Ради интереса, давайте поменяем местами базовые классы:

class NoteBook(MixinLog, Goods): pass

И мы сразу получаем ошибку, что в метод __init__ передаются четыре аргумента, а он принимает только один, так как здесь отрабатывает инициализатор уже класса MixinLog. Так что порядок следования базовых классов при множественном наследовании имеет важное значение. Первым должен идти «основной» класс и у него, как правило, инициализатор имеет несколько параметров. А далее, записываются классы, у которых, опять же, как правило, инициализаторы имеют только параметр self. Это второй важный момент. Когда мы собираемся использовать множественное наследование, то структуру классов следует продумывать так, чтобы инициализаторы вспомогательных классов имели только один параметр self, иначе будут сложности их использования. В чем они состоят? Давайте для примера пропишем в инициализаторе класса MixinLog один параметр p1

class MixinLog: def __init__(self, p1): super().__init__(1, 2)
class MixinLog2: def __init__(self, p1, p2): super().__init__() print("init MixinLog 2")

В каждом методе __init__ мы также делаем делегированный вызов инициализатора следующего базового класса. А цепочка наследования будет такой:

class NoteBook(Goods, MixinLog, MixinLog2): pass

Сейчас при запуске у нас не возникает никаких ошибок, так как последовательность MRO имеет вид: (, , , , ) То есть, мы знаем, что функция super() в классе Goods вызовет метод __init__ класса MixinLog с одним дополнительным параметром, а затем, метод __init__ класса MixinLog2 с двумя дополнительными параметрами. И мы «жестко» это прописали. Но, как вы понимаете, если хотя бы немного изменится цепочка наследования, например, так:

class NoteBook(Goods, MixinLog2, MixinLog): pass

то все нарушится и получим ошибки. Чтобы в программах при множественном наследовании не возникало проблем с зависимостью последовательности наследования дополнительных базовых классов, их инициализаторы следует создавать с одним параметром self и в каждом из них прописывать делегированный вызов инициализатора следующего класса командой:

Тогда точно никаких особых проблем при использовании множественного наследования не возникнет. Последнее, что я хочу отметить на этом занятии, это вызов методов с одинаковыми именами из базовых классов. Давайте предположим, что в классе MixinLog имеется метод print_info с тем же именем, что и в классе Goods:

def print_info(self): print("print_info класса MixinLog")

то мы обратимся к методу класса Goods, так как он записан первым в цепочке наследования и в соответствии с алгоритмом обхода MRO он будет найден первым. Но что если мы хотим вызвать этот метод из второго базового класса MixinLog? Как поступить? Сделать это можно двумя способами. Либо напрямую вызвать этот метод через класс MixinLog:

Обратите внимание, что в этом случае нам обязательно нужно указать первым аргументом ссылку на объект класса NoteBook. Либо, определить какой-либо метод в классе NoteBook (пусть он называется также):

class NoteBook(Goods, MixinLog): def print_info(self): MixinLog.print_info(self)

Источник

Читайте также:  Php binary array to string
Оцените статью