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

Наследование — один из основополагающих принципов объектно-ориентированного программирования (ООП).

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

В данной статье мы подробно рассмотрим концепцию наследования в Python, его виды, синтаксис и особенности.

Основные понятия наследования

Базовый (родительский) класс

Базовый класс, также называемый родительским или суперклассом, — это класс, от которого наследуются другие классы. Базовый класс предоставляет свойства и методы, которые могут быть унаследованы или изменены в производных классах.

Производный (дочерний) класс

Производный класс, также известный как дочерний или подкласс — это класс, который наследуется от другого класса. Дочерний класс использует все атрибуты и методы базового класса, а ещё может добавлять свои собственные или переопределять существующие.

Пример наследования классов:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "..."

class Dog(Animal):
    def speak(self):
        return f"{self.name} говорит: Гав-гав!"

В данном примере класс Animal является родительским, или базовым, а класс Dog — производным. Дочерний класс Dog унаследовал атрибут name и метод speak от базового класса Animal, но переопределил метод speak.

Магический метод __init__ тоже был унаследован, как и все остальные атрибуты.

Синтаксис наследования

Чтобы создать производный класс, необходимо указать имя (или имена через запятую) базового класса в круглых скобках после имени производного класса.

class BaseClass:
    # тело базового класса

class DerivedClass(BaseClass):
    # тело производного класса

Многоуровневое наследование

Производный класс наследуется от другого производного класса, образуя цепочку наследования.

class A:
    pass

class B(A):
    pass

class C(B):
    pass

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

Производный класс наследуется от нескольких базовых классов.

class A:
    pass

class B:
    pass

class C(A, B):
    pass

Гибридное наследование

Смешение различных видов наследования.

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

Гибридное наследование приводит к проблемам разрешения методов. Например, если мы определим атрибут name у классов B и C, то какой из них попадёт в D?

Правильный ответ - из B, так как он записан слева в списке наследования. Вообще, данная проблема в Python решается с помощью MRO (Method Resolution Order). Method Resolution Order в Python определён, в свою очередь, алгоритмом C3-линеаризации.

Посмотреть порядок поиска методов можно, введя:

print(D.mro())
# или
print(D.__mro__)

где D - это класс, для которого необходимо посмотреть Method Resolution Order.

Функция super()

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

Без super мы писали бы так:

class Shape:
    def __init__(self, color):
        self.color = color

class Circle(Shape):
    def __init__(self, color, radius):
        Shape.__init__(color)
        self.radius = radius

Однако это станет неудобным при переименовании класса, или при каком-либо ромбовидном наследовании с участием этих классов.

super() же корректно указывает на базовый класс:

class Shape:
    def __init__(self, color):
        self.color = color

class Circle(Shape):
    def __init__(self, color, radius):
        super().__init__(color)
        self.radius = radius

Здесь super().__init__(color) вызывает метод __init__ базового класса Shape.

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