Переопределение методов
Ты уже умеешь наследовать классы. Теперь узнаешь, как дочерний класс может переписать поведение родителя — и почему это один из самых мощных инструментов ООП.
🤔 Что значит «переопределить» метод?
Представь: есть родительский класс Animal (Животное). У него есть метод speak(), который возвращает просто строку «Звук». Логично — мы не знаем заранее, какое именно животное говорит.
Теперь создаём дочерний класс Dog (Собака). Собака говорит «Гав», а не «Звук». Значит нам нужно заменить метод speak() на свою версию.
💻 Смотрим на код
Создадим класс Animal и два дочерних класса — Dog и Cat:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Звук"
class Dog(Animal):
def speak(self): # переопределяем метод speak()
return "Гав!"
class Cat(Animal):
def speak(self): # переопределяем метод speak()
return "Мяу!"
dog = Dog("Бобик")
cat = Cat("Мурка")
animal = Animal("Неизвестно")
print(dog.speak()) # Гав!
print(cat.speak()) # Мяу!
print(animal.speak()) # Звук
Обрати внимание: метод называется одинаково — speak() — но каждый класс выдаёт своё. Это и есть переопределение.
🔍 Как Python решает, чей метод вызвать?
Когда ты пишешь dog.speak(), Python не гадает — он следует чёткому правилу. Называется это MRO — Method Resolution Order (порядок разрешения методов). Звучит страшно, но суть простая:
Если объект — Dog, смотрим в Dog
Поднимаемся вверх по цепочке наследования
Дальше не ищем — нашли и взяли
Посмотрим на это наглядно:
class Animal:
def speak(self):
return "Звук"
class Dog(Animal):
def speak(self):
return "Гав!"
dog = Dog("Бобик")
# Python проверяет: есть ли speak() в классе Dog? → ДА → вызываем его
# До Animal даже не доходит!
print(dog.speak()) # Гав!
# Можно явно посмотреть порядок поиска методов:
print(Dog.__mro__)
# (, , )
🎯 Когда переопределение реально нужно?
Суть идеи такая: родительский класс задаёт шаблон — говорит «у каждого животного есть метод speak()». А дочерний класс заполняет этот шаблон конкретикой — собака говорит «Гав», кошка — «Мяу».
Ещё один пример из жизни — геометрические фигуры:
class Shape:
def area(self):
return 0 # базовая реализация — ничего не знаем о конкретной фигуре
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height # конкретная формула для прямоугольника
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2 # конкретная формула для круга
rect = Rectangle(4, 5)
circle = Circle(3)
print(rect.area()) # 20
print(circle.area()) # 28.27...
Видишь паттерн? Один и тот же вызов .area() — разный результат в зависимости от класса объекта. Это называется полиморфизм, и переопределение методов — главный инструмент для его реализации.
🔄 Один вызов — разный результат
Самый крутой момент: можно положить разные объекты в список и вызвать один метод — каждый ответит по-своему:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Звук"
class Dog(Animal):
def speak(self):
return "Гав!"
class Cat(Animal):
def speak(self):
return "Мяу!"
class Duck(Animal):
def speak(self):
return "Кря!"
# Список разных животных
animals = [Dog("Бобик"), Cat("Мурка"), Duck("Кряква"), Dog("Рекс")]
for animal in animals:
print(f"{animal.name} говорит: {animal.speak()}")
# Бобик говорит: Гав!
# Мурка говорит: Мяу!
# Кряква говорит: Кря!
# Рекс говорит: Гав!
.speak(), и каждый отвечает правильно. Это и есть сила переопределения методов.
⚠️ Расширение поведения — и проблема дублирования
Иногда нужно не заменить родительский метод полностью, а добавить к нему что-то. Например, собака лает, но ещё и виляет хвостом. Без специальных инструментов это выглядит так:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} издаёт звук"
class Dog(Animal):
def speak(self):
# Хотим добавить к родительскому поведению своё
# Но super() мы ещё не знаем, поэтому копируем логику вручную:
base_sound = f"{self.name} издаёт звук" # ← дублируем код из Animal!
return base_sound + " и виляет хвостом 🐕"
dog = Dog("Бобик")
print(dog.speak())
# Бобик издаёт звук и виляет хвостом 🐕
f"{self.name} издаёт звук" написана дважды — в Animal и в Dog. Если завтра мы изменим эту строку в Animal, в Dog она останется старой. Это называется дублирование кода — плохая практика.
В следующем уроке мы познакомимся с super() — инструментом, который решает эту проблему элегантно. Он позволяет вызвать родительский метод изнутри дочернего, не копируя код.
✅ Итоги урока
- Переопределение — создаём метод с тем же именем в дочернем классе
- Python ищет метод сначала в дочернем классе, потом в родительском
- Родитель задаёт шаблон, потомок — конкретную реализацию
- Один вызов у разных объектов → разный результат = полиморфизм
- Расширение без
super()ведёт к дублированию кода — в следующем уроке исправим это