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

Что произойдет, если написать одну и ту же функцию, что и у родительского класса в Python?

2.0 Middle🔥 81 комментариев
#Python Core#Soft Skills#Архитектура и паттерны

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

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

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

Method Override (переопределение методов в Python)

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

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

class Animal:
    def speak(self):
        return "Some generic sound"

class Dog(Animal):
    def speak(self):  # Переопределяем метод родителя
        return "Woof!"

class Cat(Animal):
    def speak(self):  # Переопределяем метод родителя
        return "Meow!"

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

print(dog.speak())     # "Woof!" (метод Dog)
print(cat.speak())     # "Meow!" (метод Cat)
print(generic.speak()) # "Some generic sound" (метод Animal)

Что происходит:

  • Python ищет метод в самом классе первым
  • Если найден → использует этот метод
  • Если не найден → ищет в родительском классе
  • Если не найден там → ищет выше по MRO (Method Resolution Order)

Method Resolution Order (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  # Не переопределяет

# MRO: как Python ищет методы
print(D.mro())  # [D, B, C, A, object]

d = D()
print(d.method())  # "B" (первый в MRO после D)

# Проверить MRO
print(D.__mro__)  # (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

Override с super() — правильный способ

class Parent:
    def __init__(self, name):
        self.name = name
    
    def greet(self):
        return f"Hello, I'm {self.name}"

class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)  # Вызываем инициализацию родителя
        self.age = age
    
    def greet(self):  # Override
        # Вызываем родительский метод И добавляем свое
        parent_greeting = super().greet()
        return f"{parent_greeting} and I'm {self.age} years old"

# Использование
child = Child("Alice", 10)
print(child.greet())
# "Hello, I'm Alice and I'm 10 years old"

Почему super() важна

# ❌ НЕПРАВИЛЬНО: не используем super()
class Parent:
    def __init__(self, name):
        self.name = name

class Child(Parent):
    def __init__(self, name, age):
        # Проблема: если Parent меняется, мы забудем обновить
        Parent.__init__(self, name)  # Хардкодим имя класса
        self.age = age

# ✅ ПРАВИЛЬНО: используем super()
class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)  # Автоматически найдет Parent
        self.age = age

Полиморфизм в действии

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        """Все фигуры должны реализовать эту функцию"""
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):  # Override абстрактного метода
        return 3.14159 * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):  # Override абстрактного метода
        return self.width * self.height

class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = height
    
    def area(self):  # Override абстрактного метода
        return 0.5 * self.base * self.height

# Полиморфизм: один код работает с разными типами
def calculate_total_area(shapes: list[Shape]) -> float:
    """Работает с любыми Shape, используя их override методы"""
    total = 0
    for shape in shapes:
        total += shape.area()  # Вызывает правильный area() для каждого класса
    return total

# Использование
shapes = [
    Circle(5),
    Rectangle(4, 6),
    Triangle(3, 4)
]

print(calculate_total_area(shapes))
# Каждый shape.area() вызовет свою реализацию!

Override с разными сигнатурами

class Parent:
    def method(self, x):
        return x * 2

# Можно переопределить с разными параметрами
class Child(Parent):
    def method(self, x, y=10):  # Добавили параметр
        return x * y

child = Child()
print(child.method(5))      # 50 (использует y=10)
print(child.method(5, 20))  # 100 (использует y=20)

# Но это не очень хорошая практика!
# Лучше использовать *args и **kwargs
class Child2(Parent):
    def method(self, x, *args, **kwargs):
        result = super().method(x)  # Вызываем родителя
        # Дополнительная обработка
        return result

Method Override vs Monkey Patching

# Override (правильно)
class Logger:
    def log(self, message):
        print(f"[LOG] {message}")

class CustomLogger(Logger):
    def log(self, message):  # Override
        print(f"[CUSTOM LOG] {message}")

# Monkey patching (плохо, не делать!)
class Logger:
    def log(self, message):
        print(f"[LOG] {message}")

# ❌ Меняем метод во время выполнения (очень плохо!)
Logger.log = lambda self, message: print(f"[PATCHED] {message}")

logger = Logger()
logger.log("test")  # [PATCHED] test

# Проблемы с monkey patching:
# - Непредсказуемое поведение
# - Сложнее дебагить
# - Нарушает контракт класса
# - Может сломать код, который полагается на оригинальный метод

Проверка, переопределена ли функция

class Parent:
    def method(self):
        return "parent"

class Child1(Parent):
    pass  # Не переопределяет

class Child2(Parent):
    def method(self):  # Переопределяет
        return "child2"

# Способ 1: простой check
print(Child1.method == Parent.method)  # True (наследует)
print(Child2.method == Parent.method)  # False (переопределяет)

# Способ 2: проверить в __dict__
print('method' in Child1.__dict__)  # False
print('method' in Child2.__dict__)  # True

# Способ 3: использовать inspect
import inspect

for name, method in inspect.getmembers(Child2, predicate=inspect.ismethod):
    if name == 'method':
        # Узнаем где определен метод
        owner = next(cls for cls in Child2.__mro__ if name in cls.__dict__)
        print(f"Method defined in: {owner}")

Override в классах с множественным наследованием

class Mixin1:
    def process(self):
        return "mixin1"

class Mixin2:
    def process(self):
        return "mixin2"

class Base:
    def process(self):
        return "base"

# MRO определяет какой process() будет вызван
class Combined(Mixin1, Mixin2, Base):
    pass

print(Combined().process())  # "mixin1" (первый в MRO)
print(Combined.__mro__)
# (<class 'Combined'>, <class 'Mixin1'>, <class 'Mixin2'>, <class 'Base'>, <class 'object'>)

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

class PaymentProcessor:
    """Базовый процессор платежей"""
    
    def validate_payment(self, payment):
        return len(payment.get('card_number', '')) == 16
    
    def process(self, payment, amount):
        if not self.validate_payment(payment):
            raise ValueError("Invalid payment")
        return self._charge(amount)
    
    def _charge(self, amount):
        # Базовая реализация
        print(f"Charging {amount}")
        return True

class StripeProcessor(PaymentProcessor):
    """Override для Stripe"""
    
    def validate_payment(self, payment):
        # Stripe требует token вместо card number
        return 'token' in payment and len(payment['token']) > 0
    
    def _charge(self, amount):
        # Stripe специфичная логика
        print(f"Stripe charging {amount} using API")
        return True

class PayPalProcessor(PaymentProcessor):
    """Override для PayPal"""
    
    def validate_payment(self, payment):
        # PayPal требует email
        return '@' in payment.get('email', '')
    
    def _charge(self, amount):
        # PayPal специфичная логика
        print(f"PayPal charging {amount} using email")
        return True

# Использование
stripe = StripeProcessor()
paypal = PayPalProcessor()

# Каждый процессор использует свою validate_payment!
print(stripe.process({'token': 'tok123'}, 100))  # True
print(paypal.process({'email': 'user@example.com'}, 50))  # True

Что НЕ переопределяется

class Parent:
    def method(self):
        return "parent"

class Child(Parent):
    method = "this is not a method"  # ❌ НЕПРАВИЛЬНО!

child = Child()
try:
    child.method()  # TypeError: 'str' object is not callable
except TypeError as e:
    print(f"Error: {e}")

# Правильно:
class Child(Parent):
    def method(self):  # Это переопределяет
        return "child"

Best Practices для Override

  1. Всегда используй super() для вызова родительского метода

    class Child(Parent):
        def method(self):
            result = super().method()  # ✓
            return result + " extended"
    
  2. Сохраняй одинаковую сигнатуру метода

    # ✓ Хорошо: параметры совпадают
    class Parent:
        def method(self, x):
            pass
    
    class Child(Parent):
        def method(self, x):
            super().method(x)
    
    # ❌ Плохо: разные параметры
    class Child(Parent):
        def method(self, x, y):
            pass
    
  3. Используй abstractmethod для обязательного override

    from abc import ABC, abstractmethod
    
    class Base(ABC):
        @abstractmethod
        def required_method(self):
            pass
    
  4. Документируй что переопределяешь

    class Child(Parent):
        def process(self):
            """Override: добавляем логирование к процессу"""
            logger.info("Starting process")
            result = super().process()
            logger.info(f"Process completed: {result}")
            return result
    

Вывод: Override метода в дочернем классе — это стандартный полиморфизм в Python. Используй super() для корректной работы с наследованием.