$ sudo teach IT
🐍 Урок 3.3 · Наследование

Вызов родительского метода через super()

В прошлом уроке мы наткнулись на проблему: хочется сохранить логику родителя и добавить свою, но без копирования кода. Сегодня разберём инструмент, который это решает — super().

🤔 Вспоминаем проблему

В прошлом уроке мы видели ситуацию: класс Dog хотел добавить своё поведение к методу speak() из Animal, но вынужден был скопировать код из родителя. Вот та проблема снова:

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

    def speak(self):
        return f"{self.name} издаёт звук"


class Dog(Animal):
    def __init__(self, name, breed):
        # Хотим сохранить self.name из Animal — но как?
        # Если просто написать своё:
        self.breed = breed
        # self.name НЕ создан! Animal.__init__ не был вызван.

dog = Dog("Бобик", "Лабрадор")
print(dog.breed)   # Лабрадор — это есть
print(dog.name)    # AttributeError: 'Dog' object has no attribute 'name' ❌
Проблема: когда дочерний класс объявляет свой __init__, он полностью заменяет родительский. Атрибуты родителя (self.name) перестают создаваться, потому что родительский __init__ больше не вызывается. Объект получается «неполным».

💡 Что такое super()?

super() — это встроенная функция Python, которая возвращает «прокси-объект» родительского класса. Грубо говоря, это способ сказать Python: «Эй, возьми метод не из моего класса, а из родительского!»

Python определяет, какой именно родитель «правильный», через MRO — тот самый порядок поиска методов, который мы видели в прошлом уроке. super() просто говорит: «Иди по MRO вверх и найди там нужный метод».

super() — обращение к родительскому классу по MRO. Позволяет вызвать метод родителя изнутри дочернего класса, не указывая имя родителя явно.

🔧 Самый частый случай: super().__init__()

Девяносто процентов случаев использования super() — это именно расширение конструктора. Посмотрим, как исправить нашу проблему:

class Animal:
    def __init__(self, name):
        self.name = name       # родитель создаёт self.name


class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)   # ← вызываем родительский __init__
        self.breed = breed       # добавляем свой атрибут


dog = Dog("Бобик", "Лабрадор")
print(dog.name)   # Бобик   ✅  (создан родителем)
print(dog.breed)  # Лабрадор ✅  (создан нами)

Разберём строчку super().__init__(name) по частям:

🔑
super()
«Дай мне доступ к родительскому классу»
⚙️
.__init__(name)
«Вызови его конструктор и передай name»
✅
Итог
Родитель настраивает свою часть, дочерний — свою

🔄 super() в обычных методах (не только __init__)

super() работает в любом методе, а не только в конструкторе. Теперь решим и ту проблему с speak() — хотим добавить поведение, не копируя код родителя:

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

    def speak(self):
        return f"{self.name} издаёт звук"


class Dog(Animal):
    def speak(self):
        base = super().speak()          # вызываем speak() из Animal
        return base + " и виляет хвостом 🐕"


dog = Dog("Бобик")
print(dog.speak())
# Бобик издаёт звук и виляет хвостом 🐕

Теперь логика «{self.name} издаёт звук» живёт только в Animal. Если мы её изменим — Dog автоматически получит обновлённую версию. Дублирования нет!

⚠️ Когда super().__init__() обязателен?

Простое правило: если дочерний класс объявляет свой __init__ — вызывай super().__init__(), иначе атрибуты родителя не создадутся.

Есть одно исключение: если дочерний класс не объявляет __init__ вообще — Python автоматически использует родительский, и super() не нужен:

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


class Cat(Animal):
    # Нет своего __init__ — Python сам возьмёт родительский
    def speak(self):
        return "Мяу!"


cat = Cat("Мурка")   # вызывает Animal.__init__ автоматически
print(cat.name)      # Мурка ✅
Вывод: нет своего __init__ — не паришься. Есть свой __init__ — первой строчкой пиши super().__init__(...).

🏢 Полный пример: Employee и Manager

Разберём реалистичный пример. Есть класс Employee (сотрудник): у него есть имя и зарплата. Класс Manager (менеджер) — тоже сотрудник, но ещё управляет командой. Используем super(), чтобы не дублировать код:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def info(self):
        return f"{self.name}, зарплата: {self.salary} ₽"

    def work(self):
        return f"{self.name} выполняет задачи"


class Manager(Employee):
    def __init__(self, name, salary, team_size):
        super().__init__(name, salary)   # ← передаём name и salary родителю
        self.team_size = team_size       # ← добавляем своё поле

    def info(self):
        base_info = super().info()       # ← берём строку от родителя
        return f"{base_info}, команда: {self.team_size} чел."

    def work(self):
        base_work = super().work()       # ← берём строку от родителя
        return f"{base_work} и управляет командой"


# Создаём объекты
emp = Employee("Алиса", 80_000)
mgr = Manager("Борис", 150_000, 8)

print(emp.info())    # Алиса, зарплата: 80000 ₽
print(mgr.info())    # Борис, зарплата: 150000 ₽, команда: 8 чел.

print(emp.work())    # Алиса выполняет задачи
print(mgr.work())    # Борис выполняет задачи и управляет командой

print(mgr.name)      # Борис      ← создан родителем через super()
print(mgr.salary)    # 150000     ← создан родителем через super()
print(mgr.team_size) # 8          ← создан самим Manager

Обрати внимание: в Manager.__init__ мы не пишем self.name = name и self.salary = salary вручную — за нас это делает super().__init__(name, salary). Логика инициализации живёт в одном месте.

📜 Старый синтаксис super() — для понимания чужого кода

Если откроешь код, написанный до Python 3, или найдёшь старую статью в интернете, можешь встретить такую запись:

class Manager(Employee):
    def __init__(self, name, salary, team_size):
        super(Manager, self).__init__(name, salary)   # старый синтаксис Python 2
        self.team_size = team_size
super(Manager, self) — это Python 2-стиль. В Python 3 это писать не нужно, достаточно просто super(). Оба варианта работают в Python 3, но современный код всегда пишут без аргументов. Если видишь super(ClassName, self) — знай: это старый код, который просто не обновили.

🚨 Частая ошибка: забыть super().__init__()

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

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def info(self):
        return f"{self.name}, зарплата: {self.salary} ₽"


class Manager(Employee):
    def __init__(self, name, salary, team_size):
        # Забыли написать super().__init__(name, salary) !!!
        self.team_size = team_size

    def info(self):
        base_info = super().info()   # ← ошибка взорвётся ЗДЕСЬ
        return f"{base_info}, команда: {self.team_size} чел."


mgr = Manager("Борис", 150_000, 8)
print(mgr.info())
# AttributeError: 'Manager' object has no attribute 'name' ❌
# Хотя мы вроде ничего не трогали в info()...
Почему ошибка в info(), а не в __init__()? Потому что Python создал объект mgr без проблем — просто у него нет атрибутов name и salary. Ошибка «всплывёт» позже, когда код попытается их использовать. Это сбивает с толку при отладке!

Правило для запоминания: когда пишешь свой __init__ в дочернем классе — первая строчка всегда super().__init__(...). Сделай это рефлексом.

✅ Итоги урока

  • super() — обращение к родительскому классу через MRO
  • super().__init__(...) — расширяем конструктор: родитель настраивает своё, дочерний добавляет своё
  • super() работает в любом методе, не только в __init__
  • Если дочерний класс объявляет __init__ — вызов super().__init__() обязателен
  • Без super().__init__() атрибуты родителя не создадутся → AttributeError при использовании
  • Старый синтаксис super(ClassName, self) — Python 2, в Python 3 не нужен