Python ромбовидная структура наследований

Python — о множественном наследовании и функции super() простыми словами

Python позволяет указать для класса несколько родителей. Это называется множественным наследованием.

Например, мы хотим добавить какие-то общие свойства нескольким разным классам. Добавлять эти свойства, через класс-наследник для каждого из классов явно некрасиво, нарушает принцип DRY.

Если речь о чем-то простом, то это можно сделать через декоратор. Но если это что-то более развесистое и прикладное, то напрашивается оформить это как объект, и добавить к нужным классам как mixin. В Python нет специального способа добавлять mixin, это осуществляется через множественное наследование.

Загадка

Platypus.__init__() Mammal.__init__() Bird.__init__() Vertebrate.__init__() 

Если хоть один наследник нарушает принципы кооперативного наследования (не вызывает super() ), то метод родителя вообще не будет вызван, хотя вроде бы мы имеем явный вызов этого родителя из другого наследника.

Например, давайте закомментарим вызов super() в классе Bird (строка 8). Вывод изменится следующим образом:

Platypus.__init__() Mammal.__init__() Bird.__init__() 

Причина в том, что из Mammal.__init__ вызывается следующий по MRO класс ( Bird ), а вовсе не родитель Mammal ( Vertebrate ). Родителя ранее вызывал Bird , но мы убрали этот вызов.

В коде ниже я добавил аргументы в __init__ , чтобы проиллюстрировать сказанное выше выдаваемой ошибкой.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
class Vertebrate: def __init__(self): print('Vertebrate.__init__()') class Bird(Vertebrate): def __init__(self, beak_length): print('Bird.__init__()') super().__init__() class Mammal(Vertebrate): def __init__(self, hair_length): print('Mammal.__init__()') super().__init__() class Platypus(Mammal, Bird): def __init__(self): print('Platypus.__init__()') super().__init__(1) duckbill = Platypus() 
Platypus.__init__() Mammal.__init__() . File "animal_class_tree_arguments.py", line 13, in __init__ super().__init__() TypeError: __init__() missing 1 required positional argument: 'beak_length' 

Ошибка показывает, что в Mammal код super().__init__() пытается вызвать Bird.__init__ .

Примечание - особенности работы super()

Одним из ограничений super() является то, что не получится выполнить операции (binary operations, subscriptions и т.д.) над возвращенным объектом, даже если эти операции реализованы в родителе вызывающего класса с помощью “магических методов”.

Если выполнить операцию над экземпляром класса, то Python найдет нужный для выполнения операции “магический метод” в родителе (в примере ниже - __getitem__ для индексирования с помощью оператора [] ).

Но если попытаться выполнить операцию над объектом, возвращаемым super() , получим ошибку:

class Parent: def __getitem__(self, idx): return 0 class Child(Parent): def index_super(self, idx): return super()[idx] kid = Child() print(f'kid[0]: kid[0]>') print(f'kid.index_super(0): kid.index_super(0)>')
kid[0]: 0 . TypeError: 'super' object is not subscriptable 

Источник

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

Источник

Читайте также:  Nginx cache html files
Оцените статью