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

Применяешь ли принципы ООП

1.0 Junior🔥 201 комментариев
#Python#Софт-скиллы и мотивация

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

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

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

Применяешь ли принципы ООП

Краткий ответ

Да, я применяю принципы ООП, но селективно. В Data Science не всегда нужны классы и абстракции, которые необходимы в enterprise software. Ключ — это выбрать правильный инструмент для задачи.

Когда я ИСПОЛЬЗУЮ ООП в DS

1. Data Pipelines и ETL

from abc import ABC, abstractmethod
from typing import List, Dict, Any

class DataProcessor(ABC):
    """Абстрактный процессор данных"""
    
    @abstractmethod
    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        pass
    
    @abstractmethod
    def validate(self, data: Dict[str, Any]) -> bool:
        pass

class CSVLoader(DataProcessor):
    """Загрузчик CSV файлов"""
    
    def __init__(self, filepath: str):
        self.filepath = filepath
    
    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        import pandas as pd
        df = pd.read_csv(self.filepath)
        return {'dataframe': df}
    
    def validate(self, data: Dict[str, Any]) -> bool:
        return 'dataframe' in data and len(data['dataframe']) > 0

class DataCleaner(DataProcessor):
    """Очистка данных"""
    
    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        df = data['dataframe']
        df = df.dropna()
        df = df[df.duplicated() == False]
        return {'dataframe': df}
    
    def validate(self, data: Dict[str, Any]) -> bool:
        return len(data['dataframe']) > 0

class FeatureEngineer(DataProcessor):
    """Инженерия признаков"""
    
    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        df = data['dataframe']
        df['feature_1'] = df['col_a'] * df['col_b']
        df['feature_2'] = df['col_c'].apply(lambda x: len(str(x)))
        return {'dataframe': df}
    
    def validate(self, data: Dict[str, Any]) -> bool:
        return len(data['dataframe'].columns) >= 2

# Pipeline: чистая композиция
class DataPipeline:
    def __init__(self, processors: List[DataProcessor]):
        self.processors = processors
    
    def execute(self, initial_data: Dict[str, Any]) -> Dict[str, Any]:
        data = initial_data
        for processor in self.processors:
            print(f"Executing {processor.__class__.__name__}...")
            if not processor.validate(data):
                raise ValueError(f"Validation failed for {processor.__class__.__name__}")
            data = processor.process(data)
        return data

# Использование
pipeline = DataPipeline([
    CSVLoader('data.csv'),
    DataCleaner(),
    FeatureEngineer()
])

result = pipeline.execute({})

Плюсы здесь:

  • Легко добавлять новые обработчики
  • Код переиспользуем
  • Тестировать просто (каждый класс отвечает за свою работу)
  • Легко организовать сложные pipeline'ы

2. Модели ML с кастомной логикой

from sklearn.base import BaseEstimator, ClassifierMixin
import numpy as np

class EnsembleVoter(BaseEstimator, ClassifierMixin):
    """Простой ensemble через голосование"""
    
    def __init__(self, models: List, voting='hard'):
        self.models = models
        self.voting = voting
        self.classes_ = None
    
    def fit(self, X, y):
        for model in self.models:
            model.fit(X, y)
        self.classes_ = np.unique(y)
        return self
    
    def predict(self, X):
        predictions = np.column_stack([m.predict(X) for m in self.models])
        
        if self.voting == 'hard':
            # Мода (голосование)
            return np.array([np.bincount(pred).argmax() for pred in predictions])
        else:
            # Среднее вероятностей
            proba = np.column_stack([m.predict_proba(X) for m in self.models])
            return self.classes_[np.argmax(proba, axis=1)]
    
    def predict_proba(self, X):
        proba = np.column_stack([m.predict_proba(X) for m in self.models])
        return proba.mean(axis=1)  # Среднее

# Использование как обычной sklearn модели
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

ensemble = EnsembleVoter([
    RandomForestClassifier(n_estimators=100),
    GradientBoostingClassifier(n_estimators=100)
])

ensemble.fit(X_train, y_train)
accuracy = ensemble.score(X_test, y_test)
print(f"Ensemble accuracy: {accuracy:.4f}")

3. Configuration и Settings

from dataclasses import dataclass
from pathlib import Path
from typing import Optional

@dataclass
class ModelConfig:
    """Конфигурация для обучения модели"""
    model_name: str
    n_estimators: int
    max_depth: int
    learning_rate: float
    test_size: float = 0.2
    random_state: int = 42
    verbose: bool = True
    output_dir: Path = Path('models')
    
    def save(self, filepath: str):
        import json
        with open(filepath, 'w') as f:
            json.dump(self.__dict__, f, default=str)
    
    @classmethod
    def load(cls, filepath: str):
        import json
        with open(filepath, 'r') as f:
            return cls(**json.load(f))

# Использование
config = ModelConfig(
    model_name='gradient_boosting',
    n_estimators=100,
    max_depth=5,
    learning_rate=0.1
)

print(f"Training {config.model_name} with {config.n_estimators} estimators")

4. Data Classes для типизации

from dataclasses import dataclass
from typing import List, Optional
import pandas as pd

@dataclass
class ModelMetrics:
    """Метрики модели"""
    accuracy: float
    precision: float
    recall: float
    f1_score: float
    auc_roc: Optional[float] = None
    
    def __repr__(self) -> str:
        return f"Metrics(acc={self.accuracy:.4f}, f1={self.f1_score:.4f})"

@dataclass
class ExperimentResult:
    """Результат эксперимента"""
    experiment_id: str
    model_name: str
    metrics: ModelMetrics
    training_time: float
    hyperparameters: dict
    
    def to_dataframe(self) -> pd.DataFrame:
        return pd.DataFrame([{
            'experiment_id': self.experiment_id,
            'model': self.model_name,
            'accuracy': self.metrics.accuracy,
            'f1': self.metrics.f1_score,
            'time': self.training_time
        }])

# Использование
metrics = ModelMetrics(
    accuracy=0.85,
    precision=0.88,
    recall=0.82,
    f1_score=0.85,
    auc_roc=0.90
)

result = ExperimentResult(
    experiment_id='exp_001',
    model_name='xgboost',
    metrics=metrics,
    training_time=120.5,
    hyperparameters={'max_depth': 5, 'learning_rate': 0.1}
)

print(result.metrics)  # Metrics(acc=0.8500, f1=0.8500)

Когда я НЕ использую ООП (и это правильно)

1. Исследовательские ноутбуки

# ЭТО НОРМАЛЬНО для Jupyter!
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# Прямой код, без классов
df = pd.read_csv('data.csv')
df = df.dropna()
df['new_feature'] = df['a'] * df['b']

plt.hist(df['new_feature'])
plt.show()

# Классы здесь были бы избыточны

2. Простые скрипты обработки данных

# ООП добавит больше кода, но не больше функциональности
def load_and_clean(filepath):
    import pandas as pd
    df = pd.read_csv(filepath)
    return df.dropna()

def add_features(df):
    df['feature'] = df['col_a'] * df['col_b']
    return df

def train_model(X, y):
    from sklearn.ensemble import RandomForestClassifier
    model = RandomForestClassifier()
    model.fit(X, y)
    return model

# Процедурный подход здесь уместен
df = load_and_clean('data.csv')
df = add_features(df)
model = train_model(df.drop('target', axis=1), df['target'])

Мой подход: Pragmatic OOP

Я использую ООП когда:

  • ✓ Code будет переиспользован
  • ✓ Нужна абстракция для разных реализаций
  • ✓ Нужна типизация и контроль (особенно в production)
  • ✓ Код сложный и нужна организация
  • ✓ Работаю с командой (нужна структура)

Я НЕ использую ООП когда:

  • ✗ Однократный скрипт или ноутбук
  • ✗ Простая обработка данных (pandas достаточно)
  • ✗ Быстрый прототип
  • ✗ Функциональный подход проще

Пример: правильное применение ООП

# ДО: процедурный код, трудно расширять
def train_xgboost(X, y):
    import xgboost as xgb
    model = xgb.XGBClassifier()
    model.fit(X, y)
    return model

def train_lightgbm(X, y):
    import lightgbm as lgb
    model = lgb.LGBMClassifier()
    model.fit(X, y)
    return model

def train_random_forest(X, y):
    from sklearn.ensemble import RandomForestClassifier
    model = RandomForestClassifier()
    model.fit(X, y)
    return model

# ПОСЛЕ: ООП, легко добавлять новые модели
from abc import ABC, abstractmethod

class ModelTrainer(ABC):
    @abstractmethod
    def train(self, X, y):
        pass

class XGBoostTrainer(ModelTrainer):
    def __init__(self, params=None):
        self.params = params or {}
    
    def train(self, X, y):
        import xgboost as xgb
        model = xgb.XGBClassifier(**self.params)
        model.fit(X, y)
        return model

class LightGBMTrainer(ModelTrainer):
    def __init__(self, params=None):
        self.params = params or {}
    
    def train(self, X, y):
        import lightgbm as lgb
        model = lgb.LGBMClassifier(**self.params)
        model.fit(X, y)
        return model

# Теперь легко использовать любой trainer
trainers = [
    XGBoostTrainer({'max_depth': 5}),
    LightGBMTrainer({'max_depth': 5})
]

for trainer in trainers:
    model = trainer.train(X_train, y_train)
    score = model.score(X_test, y_test)
    print(f"{trainer.__class__.__name__}: {score:.4f}")

Резюме

Применяю ООП в Data Science для:

  1. Production кода и pipelines
  2. Сложных систем с многими компонентами
  3. Кода, который разделяю с командой
  4. Абстракций (процессоры, модели, конфиги)

НЕ применяю ООП для:

  1. Исследовательских ноутбуков
  2. Быстрых прототипов
  3. Простых скриптов
  4. Когда функциональный подход проще

Главный принцип: pragmatism over dogmatism. ООП — это инструмент, а не закон. Выбираю инструмент, который решает задачу с минимальной сложностью.

Применяешь ли принципы ООП | PrepBro