Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое наследование?
Наследование — это один из четырёх основных принципов объектно-ориентированного программирования, который позволяет создавать новые классы на основе существующих. Дочерний класс (наследник) получает и переиспользует методы и атрибуты родительского класса (базового).
Основная концепция
Наследование создаёт иерархию классов, где:
- Базовый класс (Parent/Base) — содержит общую функциональность
- Дочерний класс (Child/Derived) — расширяет или переопределяет функциональность родителя
- IS-A отношение — дочерний класс "является" частным случаем базового
Простой пример
class Animal:
"""Базовый класс"""
def __init__(self, name: str):
self.name = name
def make_sound(self) -> str:
return "Some sound"
def info(self) -> str:
return f"I am {self.name}"
class Dog(Animal):
"""Дочерний класс наследует от Animal"""
def make_sound(self) -> str: # Переопределение метода
return "Woof!"
class Cat(Animal):
"""Другой дочерний класс"""
def make_sound(self) -> str:
return "Meow!"
# Использование
dog = Dog("Rex")
cat = Cat("Whiskers")
print(dog.info()) # I am Rex
print(dog.make_sound()) # Woof!
print(cat.make_sound()) # Meow!
Типы наследования
1. Одиночное наследование
class Vehicle:
def start(self):
print("Starting engine")
class Car(Vehicle): # Car наследует от Vehicle
def honk(self):
print("Honk honk!")
car = Car()
car.start() # Метод из родителя
car.honk() # Собственный метод
2. Множественное наследование
class Swimmer:
def swim(self):
return "Swimming"
class Flyer:
def fly(self):
return "Flying"
class Duck(Swimmer, Flyer): # Duck наследует от обоих
pass
duck = Duck()
print(duck.swim()) # Swimming
print(duck.fly()) # Flying
3. Многоуровневое наследование
class Animal:
pass
class Mammal(Animal):
def warm_blood(self):
return True
class Dog(Mammal):
def bark(self):
return "Woof"
dog = Dog()
print(dog.warm_blood()) # True (из Mammal)
print(dog.bark()) # Woof (собственное)
Переопределение методов (Override)
class Shape:
def area(self) -> float:
raise NotImplementedError("Subclass must implement area")
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float: # Переопределение
return 3.14 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width: float, height: float):
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
# Полиморфизм в действии
shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
print(shape.area()) # Вызывает нужный метод для каждого класса
Использование super()
class Parent:
def __init__(self, name: str):
self.name = name
def greet(self):
return f"Hello, I'm {self.name}"
class Child(Parent):
def __init__(self, name: str, age: int):
super().__init__(name) # Вызовем __init__ родителя
self.age = age
def greet(self):
parent_greeting = super().greet() # Вызовем 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
Абстрактные классы
from abc import ABC, abstractmethod
class DataProcessor(ABC):
"""Абстрактный базовый класс"""
@abstractmethod
def process(self, data):
"""Дочерние классы ДОЛЖНЫ реализовать этот метод"""
pass
def log(self, message: str):
print(f"[LOG] {message}")
class CSVProcessor(DataProcessor):
def process(self, data):
# Конкретная реализация для CSV
return f"Processing CSV: {data}"
class JSONProcessor(DataProcessor):
def process(self, data):
# Конкретная реализация для JSON
return f"Processing JSON: {data}"
# Использование
processors = [CSVProcessor(), JSONProcessor()]
for processor in processors:
processor.log("Starting")
print(processor.process("data"))
Преимущества наследования
- Переиспользование кода — не дублируешь общую функциональность
- Иерархия и организация — логичная структура классов
- Полиморфизм — работаешь с разными типами через общий интерфейс
- Расширяемость — легко добавлять новые классы
- Поддерживаемость — изменения в базовом классе автоматически наследуются
Проблемы и антипаттерны
Проблема хрупкого базового класса
# ❌ ПЛОХО: Глубокая иерархия, сложная для изменения
class A: pass
class B(A): pass
class C(B): pass
class D(C): pass
class E(D): pass
# ✅ ХОРОШО: Плоская структура с композицией
class Processor:
def __init__(self, validator, transformer):
self.validator = validator
self.transformer = transformer
Множественное наследование может быть сложным
# ❌ Алмазная проблема
class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
class C(A):
def method(self):
print("C")
class D(B, C):
pass
d = D()
d.method() # Какой метод вызовется? B благодаря MRO (Method Resolution Order)
Наследование в Data Engineering
class DataSource(ABC):
@abstractmethod
def read(self) -> list:
pass
class CSVDataSource(DataSource):
def __init__(self, filepath: str):
self.filepath = filepath
def read(self) -> list:
# Чтение CSV
return []
class DatabaseDataSource(DataSource):
def __init__(self, connection_string: str):
self.connection = connection_string
def read(self) -> list:
# Чтение из БД
return []
# Используем полиморфно
class Pipeline:
def __init__(self, source: DataSource):
self.source = source
def execute(self):
data = self.source.read()
return data
Принцип Лиcкова (SOLID — L)
Дочерний класс должен корректно заменять родительский класс без нарушения корректности программы.
Когда использовать наследование
✅ Используй наследование когда:
- Есть чёткое IS-A отношение
- Дочерний класс расширяет функциональность
- Нужно переиспользовать общий код
❌ Не используй наследование когда:
- Нужно просто добавить функциональность (используй композицию)
- Иерархия становится слишком глубокой
- Есть множественное наследование без необходимости
Помни: Композиция часто лучше наследования.
Наследование — мощный инструмент для создания гибких и переиспользуемых систем, но его нужно применять разумно.