Вызов родительского метода через 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().__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(). Оба варианта работают в 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()...
mgr без проблем — просто у него нет атрибутов name и salary. Ошибка «всплывёт» позже, когда код попытается их использовать. Это сбивает с толку при отладке!
Правило для запоминания: когда пишешь свой __init__ в дочернем классе — первая строчка всегда super().__init__(...). Сделай это рефлексом.
✅ Итоги урока
super()— обращение к родительскому классу через MROsuper().__init__(...)— расширяем конструктор: родитель настраивает своё, дочерний добавляет своёsuper()работает в любом методе, не только в__init__- Если дочерний класс объявляет
__init__— вызов super().__init__() обязателен - Без
super().__init__()атрибуты родителя не создадутся →AttributeErrorпри использовании - Старый синтаксис
super(ClassName, self)— Python 2, в Python 3 не нужен