Три принципа ООП: наследование и полиморфизм

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

Наследование позволяет создать новый класс на основе уже существующего (т.е. унаследовать от его). Базовый класс (тот, который мы расширяем) называется родителем (parent), а новый - наследником (child). Пример этого явления - ниже.

import random

class Dog:
    """Описывает собаку, которая умеет лаять в зависимости от голода.
    Голод выбирается случайным образом"""

    def __init__(self):
        # свойство голод - защищенное - оно недоступно извне, но доступно наследникам
        self._hunger = random.randint(1, 5)

    def voice(self):
        """Чем голоднее собака, тем дольше лает!"""
        print("wow " * self._hunger)

# Наследуем кошку от собаки, в наследство кошка получает метод voice
class Cat(Dog):
    """Описывает кошку. Помимо голода у нее есть милота."""

    def __init__(self):
        self._hunger = random.randint(1, 5)
        self._cuttiness = random.randint(1, 5)

if __name__ == "__main__":
    c = Cat()

    c.voice()

Если вам кажется, что что-то не так, вы правы! Обязательно смотрите следующую страницу!

Наследование - это один из отличных способов повторного использования кода. Ранее мы встречались с этим явлением, когда использовали функции. Функции позволяли сократить код, избавляясь от однотипных операций. Используя наследование можно точно так же избавиться от похожих частей в разных классах.

Полиморфизм

Полиморфизм позволяет наследнику изменять поведение родителя. Иными словами, можно пересоздавать методы родителя в наследнике и это не будет ошибкой. Причем код родителя можно повторно использовать, "добавив" к нему что-то новое.

В предыдущем примере кошке в наследство от родителя досталась функция голос, которая работает не так, как хотелось бы - кошка лает!!! А еще у нас есть фактологическая ошибка: кошка - это никак не подвил собаки, это другое животное. Но интерфейс (набор методов) у них правда одинаковый. Давайте исправим это!

import random

import pytest

class Animal():
    """Описывает абстрактный класс Животное, может лаять и обладает уровнем голода."""

    # То что класс абстрактный, означает, что небывает просто животных, бывают только конкретные.
    def __init__(self):
        self._hunger = random.randint(1, 5)

    def voice(self):
        raise NotImplementedError

# Собака наследует от животного, реализует его интерфейс
class Dog(Animal):
    """Описывает собаку - животное, которое умеет лаять."""

    def __init__(self):
        # Собаке нужен только голод, поэтому вызываем конструктор родителя - животного.
        super().__init__()

    def voice(self):
        """Чем голоднее собака, тем дольше лает!"""
        print("wow " * self._hunger)

# Кошка тоже наследует от животного, реализует его интерфейс
class Cat(Animal):
    """Описывает кошку. Помимо голода у нее есть милота и она умеет мяукать."""

    def __init__(self):
        # вызываем конструктор родителя и добавляем свойство милота
        super().__init__()
        self._cuttiness = random.randint(1, 5)

    def voice(self):
        """Чем голоднее собака, тем дольше лает!"""
        print(("m" + ("e" * self._cuttiness) + "ow") * self._hunger)

if __name__ == "__main__":
    sharik = Dog()
    sharik.voice()

    barsik = Cat()
    barsik.voice()

    with pytest.raises(TypeError):
        # животное создать нельзя
        strange_entity = Animal()

Результат:

wow wow wow wow 
meow meow meow meow meow