← Назад к вопросам

Что будет, если в дочернем классе определить метод, который был в родительском?

2.3 Middle🔥 161 комментариев
#FastAPI и Flask

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Переопределение методов в дочернем классе (Override)

Это классический механизм переопределения методов (method override) в объектно-ориентированном программировании. Когда дочерний класс определяет метод с тем же именем, что и в родительском, дочерний метод полностью заменяет родительский.

Базовый пример

class Animal:
    def speak(self):
        return "Издаёт звук"
    
    def move(self):
        return "Движется"

class Dog(Animal):
    def speak(self):  # Переопределяем метод speak
        return "Гав-гав!"
    # метод move наследуется из Animal

class Cat(Animal):
    def speak(self):  # Переопределяем метод speak
        return "Мяу!"
    
    def move(self):  # Переопределяем и этот метод
        return "Движется грациозно"

# Использование
dog = Dog()
cat = Cat()
animal = Animal()

print(dog.speak())     # "Гав-гав!" — используется метод Dog
print(cat.speak())     # "Мяу!" — используется метод Cat
print(animal.speak())  # "Издаёт звук" — используется метод Animal

print(dog.move())      # "Движется" — унаследовано из Animal
print(cat.move())      # "Движется грациозно" — переопределено в Cat

Полиморфизм благодаря переопределению

Это самое мощное свойство наследования — полиморфизм. Одинаковый код работает с разными типами:

def make_animal_speak(animal: Animal) -> str:
    """Работает с любым Animal, включая Dog и Cat"""
    return animal.speak()

animals = [Dog(), Cat(), Animal()]

for animal in animals:
    print(make_animal_speak(animal))
# Выведет:
# Гав-гав!
# Мяу!
# Издаёт звук

Потому что Python использует динамическую диспетчеризацию — во время выполнения выбирается нужный метод в зависимости от реального типа объекта.

Использование super() для вызова родительского метода

Часто нужно расширить функциональность родительского метода, а не полностью его заменить. Для этого используется super():

class Animal:
    def __init__(self, name: str):
        self.name = name
    
    def speak(self) -> str:
        return f"{self.name} издаёт звук"

class Dog(Animal):
    def __init__(self, name: str, breed: str):
        super().__init__(name)  # Вызываем конструктор родителя
        self.breed = breed
    
    def speak(self) -> str:
        parent_speak = super().speak()  # Получаем результат из родителя
        return f"{parent_speak} (сорока: {self.breed})"

dog = Dog("Рекс", "Лабрадор")
print(dog.speak())  # "Рекс издаёт звук (сорока: Лабрадор)"

Порядок поиска методов (MRO — Method Resolution Order)

Eсли есть множественное наследование, Python использует MRO для определения, какой метод вызывать:

class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B"

class C(A):
    def method(self):
        return "C"

class D(B, C):
    pass  # Не переопределяет method

d = D()
print(d.method())  # "B" — потому что B раньше C в MRO
print(D.__mro__)   # Показывает порядок поиска
# (<class D>, <class B>, <class C>, <class A>, <class object>)

Переопределение специальных методов (dunder methods)

То же самое работает со специальными методами:

class Vehicle:
    def __init__(self, model: str):
        self.model = model
    
    def __str__(self) -> str:
        return f"Транспорт: {self.model}"
    
    def __repr__(self) -> str:
        return f"Vehicle({self.model})"
    
    def __len__(self) -> int:
        raise NotImplementedError("Подклассы должны определить __len__")

class Car(Vehicle):
    def __init__(self, model: str, seats: int):
        super().__init__(model)
        self.seats = seats
    
    def __str__(self) -> str:
        return f"Автомобиль {self.model} ({self.seats} мест)"
    
    def __len__(self) -> int:
        return self.seats

car = Car("Toyota", 5)
print(str(car))    # "Автомобиль Toyota (5 мест)"
print(repr(car))   # "Vehicle(Toyota)" — не переопределили __repr__
print(len(car))    # 5 — переопределили __len__

Проверка переопределения: hasattr и isinstance

Можно проверить, переопределён ли метод:

class Parent:
    def method(self):
        pass

class Child(Parent):
    def method(self):
        pass

# Проверяем, есть ли метод
child = Child()
if hasattr(child, method):
    print("Метод есть")

# Проверяем тип
if isinstance(child, Parent):
    print("Child — это тоже Parent")

# Проверяем, какая версия метода используется
print(Child.method)   # <function Child.method at 0x...>
print(Parent.method)  # <function Parent.method at 0x...>
print(Child.method is Parent.method)  # False — разные методы

Абстрактные методы (Abstract Base Classes)

Для строгого контроля переопределения используются ABC:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        """Площадь фигуры"""
        pass
    
    @abstractmethod
    def perimeter(self) -> float:
        """Периметр фигуры"""
        pass

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius
    
    def area(self) -> float:
        return 3.14 * self.radius ** 2
    
    def perimeter(self) -> float:
        return 2 * 3.14 * self.radius

# ✓ Работает
circle = Circle(5)

# ❌ Ошибка — не все методы переопределены
class IncompleteShape(Shape):
    def area(self) -> float:
        return 0
    # perimeter не переопределён — TypeError!

Практический пример: различные обработчики

class RequestHandler(ABC):
    def handle(self, request: dict) -> dict:
        # Валидируем
        if not self.validate(request):
            return {"error": "Invalid request"}
        # Обрабатываем
        return self.process(request)
    
    @abstractmethod
    def validate(self, request: dict) -> bool:
        pass
    
    @abstractmethod
    def process(self, request: dict) -> dict:
        pass

class UserHandler(RequestHandler):
    def validate(self, request: dict) -> bool:
        return "user_id" in request and isinstance(request["user_id"], int)
    
    def process(self, request: dict) -> dict:
        user_id = request["user_id"]
        return {"user": f"User {user_id}"}

class PaymentHandler(RequestHandler):
    def validate(self, request: dict) -> bool:
        return "amount" in request and request["amount"] > 0
    
    def process(self, request: dict) -> dict:
        amount = request["amount"]
        return {"paid": amount, "status": "success"}

# Использование полиморфизма
handlers = [UserHandler(), PaymentHandler()]
requests = [
    {"user_id": 123},
    {"amount": 50.00}
]

for handler, request in zip(handlers, requests):
    result = handler.handle(request)  # Вызывается нужная версия
    print(result)

Ключевые моменты

  • Переопределение полностью заменяет метод родителя в дочернем классе
  • super() позволяет вызвать родительскую версию при необходимости
  • MRO определяет порядок поиска в сложных иерархиях
  • Полиморфизм — это сила переопределения, позволяющая писать гибкий код
  • ABC (Abstract Base Classes) помогают контролировать, что должно быть переопределено

Это один из столпов ООП и один из самых важных механизмов для написания расширяемого и поддерживаемого кода.