Наследование — один из ключевых принципов ООП, позволяющий создавать новый класс (дочерний) на основе существующего (родительского). Дочерний класс наследует атрибуты и методы родительского класса, что способствует повторному использованию кода и созданию иерархии классов.

Отношение между классами при наследовании часто описывается как “является” (is-a). Например, “Собака является Животным”.

4.1. Базовый Синтаксис

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

# Родительский (базовый) класс
class Animal:
    def __init__(self, name):
        self.name = name
        print(f"Создано животное: {self.name}")
 
    def speak(self):
        raise NotImplementedError("Дочерний класс должен реализовать этот метод")
 
    def eat(self):
        print(f"{self.name} ест.")
 
# Дочерний класс, наследующий от Animal
class Dog(Animal):
    def speak(self): # Переопределяем метод speak
        print(f"{self.name} говорит Гав!")
 
# Другой дочерний класс
class Cat(Animal):
    def speak(self): # Переопределяем метод speak
        print(f"{self.name} говорит Мяу!")
 
    def purr(self): # Добавляем свой уникальный метод
        print(f"{self.name} мурлычет.")
 
# Создаем объекты дочерних классов
my_dog = Dog("Рекс") # Выведет: Создано животное: Рекс
my_cat = Cat("Барсик") # Выведет: Создано животное: Барсик
 
# Вызываем унаследованный метод eat
my_dog.eat() # Рекс ест.
my_cat.eat() # Барсик ест.
 
# Вызываем переопределенный метод speak
my_dog.speak() # Рекс говорит Гав!
my_cat.speak() # Барсик говорит Мяу!
 
# Вызываем собственный метод Cat
my_cat.purr() # Барсик мурлычет.
# my_dog.purr() # AttributeError: 'Dog' object has no attribute 'purr'

4.2. Функция super()

Функция super() позволяет дочернему классу вызывать методы родительского класса. Это особенно полезно, когда нужно расширить, а не полностью заменить, функциональность родительского метода (например, __init__).

class Employee:
    def __init__(self, first, last, pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = f"{first}.{last}@company.com"
        print(f"Создан сотрудник {self.first} {self.last}")
 
class Developer(Employee):
    def __init__(self, first, last, pay, prog_lang):
        # Вызываем __init__ родительского класса (Employee)
        super().__init__(first, last, pay)
        # Добавляем свой атрибут
        self.prog_lang = prog_lang
        print(f"Сотрудник {self.first} является разработчиком")
 
dev1 = Developer("Анна", "Петрова", 80000, "Python")
# Вывод:
# Создан сотрудник Анна Петрова
# Сотрудник Анна является разработчиком
 
print(dev1.email) # anna.petrova@company.com
print(dev1.prog_lang) # Python

4.3. Переопределение Методов (Method Overriding)

Дочерний класс может предоставить свою собственную реализацию метода, который уже существует в родительском классе. Это называется переопределением. Как показано в первом примере, классы Dog и Cat переопределили метод speak класса Animal.

Если нужно вызвать и родительскую версию метода внутри переопределенного, используется super().method_name().

class Parent:
    def greet(self):
        print("Привет от родителя!")
 
class Child(Parent):
    def greet(self):
        super().greet() # Вызываем родительский метод
        print("... и привет от ребенка!")
 
child_obj = Child()
child_obj.greet()
# Вывод:
# Привет от родителя!
# ... и привет от ребенка!

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

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

class Swimmer:
    def swim(self):
        print("Плавает")
 
class Flyer:
    def fly(self):
        print("Летает")
 
# Наследует от обоих классов
class Duck(Swimmer, Flyer):
    def quack(self):
        print("Крякает")
 
duck = Duck()
duck.swim() # Плавает
duck.fly()  # Летает
duck.quack() # Крякает

MRO (Method Resolution Order - Порядок разрешения методов): При множественном наследовании Python определяет порядок, в котором ищутся методы и атрибуты в иерархии классов. Этот порядок можно посмотреть с помощью атрибута __mro__ или метода mro().

print(Duck.__mro__)
# Вывод (примерный):
# (<class '__main__.Duck'>, <class '__main__.Swimmer'>, <class '__main__.Flyer'>, <class 'object'>)

Это означает, что Python сначала ищет метод в Duck, затем в Swimmer, затем в Flyer, и в последнюю очередь в базовом классе object.

Осторожно: Множественное наследование может усложнить код и привести к проблемам (например, “проблема ромба” - diamond problem), если не использовать его аккуратно. Часто предпочтительнее использовать композицию (когда объект содержит экземпляры других классов) или миксины (классы, предназначенные для добавления функциональности другим классам через наследование).

〰〰〰 𓆝 𓆟 𓆞 𓆝 𓆟 𓆝 𓆟 𓆞 〰〰〰