+--------------------------+
|.------------------------.|
|| kee_reel@blog:~$ cd    ||
|| си python терминал     ||
|| opengl sql             ||
||                        ||
||                обо_мне ||
|.------------------------.|
+-::--------------------::-+
.--------------------------.
 // /ooooooooooooooooooooo\\ \\ 
 // /ooooooooooooooooooooooo\\ \\ 
//------------------------------\\
\\------------------------------//

Python. Наследование классов

Эта статья является составной частью статьи про ООП – если ты хочешь понять всю картину, то лучше начать с неё.

Наследование – позволяет связать разные классы в иерархию таким образом, что мы можем в старших классах определить базовые функции, и переиспользовать их во всех классах наследниках.

Вот пример наследования:

Наследование

У нас есть базовый класс Seat:

class Seat:
    def __init__(self, seat):
        self.seat = seat

    def take_a_seat(self):
        desc = self.get_description()
        print(f'Ты сел на {desc}')

    def get_description(self):
        return f'сиденье из {self.seat}'

В классе Seat есть:

  • Поле seat – строка, с названием материала сиденья
  • Метод take_a_seat – метод, который выводит строку, с описанием на что мы сели
  • Метод get_description – метод, который возвращает строку с описанием того, на что мы сели

Круто, но на сиденьи не получится нормально посидеть – введём класс Tabouret, отнаследованный от класса Seat:

# В скобочках указываем родительский класс Seat
class Tabouret(Seat):
    def __init__(self, seat, legs):
        Seat.__init__(self, seat)
        self.legs = legs
    # Переопределяем родительский метод get_description
    def get_description(self):
        return f'табурет у которого {self.legs} ножки, а сиденье из {self.seat}'

Обрати внимание, что мы вызываем конструктор базового класса:

Seat.__init__(self, seat)

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

Класс Tabouret:

  • Добавляет поле legs – число ножек
  • Переопределяет метод get_description – теперь, если у объекта класса Tabouret вызвать метод get_description, то вызовется его реализация из класса Tabouret, а не из класса Seat
  • Поле seat и метод take_a_seat наследуются из класса Seat

Переопределение метода родительского класса это тоже важное понятие – запомни его.

А как сделать стул? Введём класс Chair и отнаследуем от класса Tabouret:

class Chair(Tabouret):
    def __init__(self, seat, legs, back):
        Tabouret.__init__(self, seat, legs)
        self.back = back

    def get_description(self):
        return f'стул у которого {self.legs} ножки, сиденье из {self.seat}, а спинка из {self.back}'

Класс Chair:

  • Добавляет поле _back – строка, с названием материала спинки
  • Переопределяет метод get_description – теперь, если у объекта класса Chair вызвать метод get_description, то вызовется его реализация из класса Chair, а не из класса Tabouret
  • Поле legs наследуются из класса Tabouret
  • Поле seat и метод take_a_seat наследуются из класса Seat

А что с диваном? Введём класс Couch и отнаследуем его от класса Seat:

class Couch(Seat):
    def __init__(self, seat):
        Seat.__init__(self, seat)

    def get_description(self):
        return f'диван из {self.seat}'

Классы Couch и Chair имеют общий родительский класс Seat, но в остальном обладают разными свойствами.

Класс Couch:

  • Переопределяет метод get_description – теперь, если у объекта класса Couch вызвать метод get_description, то вызовется его реализация из класса Couch, а не из класса Seat

Давай теперь создадим объекты каждого класса, и попробуем дёрнуть их методы take_a_seat:

seat = Seat('паралона')
tabouret = Tabouret('металла', 3)
chair = Chair('дерева', 4, 'дерева')
couch = Couch('экокожи')

seat.take_a_seat()
# Вывод: Ты сел на сиденье из паралона
tabouret.take_a_seat()
# Вывод: Ты сел на табурет у которого 3 ножки, а сиденье из металла
chair.take_a_seat()
# Вывод: Ты сел на стул у которого 4 ножки, сиденье из дерева, а спинка из дерева
couch.take_a_seat()
# Вывод: Ты сел на диван из экокожи

Окей, а зачем мы это всё сделали?

Тебе не надо дублировать одинаковую логику в разных классах – логика пишется один раз в родительском классе, и переиспользуется во всех дочерних классах.

Например, я захочу поменять вид строки в take_a_seat – для этого я залезу в код класса Seat:

def take_a_seat(self):
    desc = self.get_description()
    print(f'Ты испытываешь нестерпимое желание сесть на {desc}')

И, не меняя ничего другого, у меня изменится вывод для всех других классов:

seat.take_a_seat()
# Вывод: Ты испытываешь нестерпимое желание сесть на сиденье из паралона
tabouret.take_a_seat()
# Вывод: Ты испытываешь нестерпимое желание сесть на табурет у которого 3 ножки, а сиденье из металла
chair.take_a_seat()
# Вывод: Ты испытываешь нестерпимое желание сесть на стул у которого 4 ножки, сиденье из дерева, а спинка из дерева
couch.take_a_seat()
# Вывод: Ты испытываешь нестерпимое желание сесть на диван из экокожи

Это очень удобно, если работаешь со множеством классов.

Заключение

Итого, мы изучили:

  • Наследование
  • Родительский класс
  • Дочерний класс
  • Конструктор родительского класса
  • Переопределение метода

И, ещё раз, смысл наследования:

Тебе не надо дублировать одинаковую логику в разных классах – логика пишется один раз в родительском классе, и переиспользуется во всех дочерних классах.

Если что – пиши, я помогу и постараюсь объяснить лучше.

Если ты ещё полон сил, то вернись к статье про ООП, и продолжи постижение ООП.