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

Расскажи про концепции ОПП

1.0 Junior🔥 181 комментариев
#Python

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

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

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

Концепции Объектно-ориентированного программирования (ООП)

Объектно-ориентированное программирование — это парадигма, которая организует код вокруг объектов и классов, а не функций и данных. Для Data Engineer это критически важно при разработке ETL систем, обработчиков данных и микросервисов.

1. Класс и Объект

Класс — это шаблон/чертёж, определяющий структуру и поведение. Объект — это конкретный экземпляр класса.

# Определение класса
class DataPipeline:
    def __init__(self, name, source, destination):
        self.name = name
        self.source = source
        self.destination = destination
    
    def run(self):
        return f'Running {self.name}'

# Создание объектов
pipeline1 = DataPipeline('etl_users', 'postgresql', 's3')
pipeline2 = DataPipeline('etl_events', 'kafka', 'redshift')

print(pipeline1.run())  # Running etl_users

2. Инкапсуляция (Encapsulation)

Скрывание внутренних деталей реализации, предоставление только необходимого интерфейса.

class Database:
    def __init__(self, host, port, password):
        self._host = host
        self._port = port
        self.__password = password  # приватное поле
    
    def connect(self):
        # Скрываем детали подключения
        return f'Connected to {self._host}:{self._port}'
    
    def _validate_password(self):
        # Вспомогательный метод (начинается с _)
        return len(self.__password) > 8

# Использование
db = Database('localhost', 5432, 'secret123')
print(db.connect())  # OK
print(db._password)  # Ошибка - приватное!

3. Наследование (Inheritance)

Класс может наследовать свойства и методы другого класса.

# Базовый класс
class DataSource:
    def __init__(self, name):
        self.name = name
    
    def fetch(self):
        raise NotImplementedError()

# Наследование
class PostgreSQLSource(DataSource):
    def fetch(self):
        return f'Fetching from PostgreSQL: {self.name}'

class S3Source(DataSource):
    def fetch(self):
        return f'Fetching from S3: {self.name}'

# Использование
sources = [
    PostgreSQLSource('users_table'),
    S3Source('events_logs')
]

for source in sources:
    print(source.fetch())  # Полиморфизм!

4. Полиморфизм (Polymorphism)

Один интерфейс, разные реализации. Позволяет работать с объектами разных типов единообразно.

# Полиморфный интерфейс
class Transformer:
    def transform(self, data):
        raise NotImplementedError()

class UppercaseTransformer(Transformer):
    def transform(self, data):
        return [row.upper() for row in data]

class FilterTransformer(Transformer):
    def transform(self, data):
        return [row for row in data if len(row) > 3]

# Generic функция, работающая с любым Transformer
def apply_pipeline(data, transformers):
    result = data
    for transformer in transformers:
        result = transformer.transform(result)
    return result

# Использование
data = ['hi', 'hello', 'world', 'ok']
pipeline = [
    FilterTransformer(),
    UppercaseTransformer()
]

print(apply_pipeline(data, pipeline))  # ['HELLO', 'WORLD']

5. Абстракция (Abstraction)

Выделение существенных признаков, скрытие деталей реализации.

from abc import ABC, abstractmethod

# Абстрактный базовый класс
class ETLJob(ABC):
    def __init__(self, job_id):
        self.job_id = job_id
        self.status = 'pending'
    
    @abstractmethod
    def extract(self):
        pass
    
    @abstractmethod
    def transform(self):
        pass
    
    @abstractmethod
    def load(self):
        pass
    
    def run(self):
        self.extract()
        self.transform()
        self.load()
        self.status = 'completed'

# Конкретная реализация
class UserETL(ETLJob):
    def extract(self):
        return 'Extracting users...'
    
    def transform(self):
        return 'Transforming users...'
    
    def load(self):
        return 'Loading users...'

# Нельзя создать объект абстрактного класса
# job = ETLJob('123')  # ERROR!

job = UserETL('123')
job.run()  # OK

6. Композиция (Composition)

"Содержание объекта вместо наследования" — часто лучше, чем наследование.

# Плохо: глубокое наследование
class Animal:
    def move(self): pass

class Mammal(Animal): pass
class Dog(Mammal): pass

# Хорошо: композиция
class Logger:
    def log(self, message):
        print(f'[LOG] {message}')

class DataValidator:
    def validate(self, data):
        return len(data) > 0

class DataProcessor:
    def __init__(self):
        self.logger = Logger()
        self.validator = DataValidator()
    
    def process(self, data):
        self.logger.log(f'Processing {len(data)} rows')
        if self.validator.validate(data):
            return f'Processed {len(data)} rows'
        return 'Invalid data'

processor = DataProcessor()
print(processor.process([1, 2, 3]))

7. Принцип SOLID

S (Single Responsibility) — одна класс, одна ответственность

# ❌ Плохо: слишком много ответственности
class UserManager:
    def create_user(self): pass
    def send_email(self): pass  # Не касается управления пользователями
    def log_activity(self): pass  # Не касается управления пользователями

# ✅ Хорошо: разделить ответственность
class UserManager:
    def create_user(self): pass

class EmailService:
    def send_email(self): pass

class Logger:
    def log_activity(self): pass

O (Open/Closed) — открыто для расширения, закрыто для модификации

# Плохо: нужно менять класс для каждого нового формата
class DataExporter:
    def export(self, data, format):
        if format == 'csv':
            return self._export_csv(data)
        elif format == 'json':
            return self._export_json(data)

# Хорошо: добавлять новые форматы без изменения класса
class Exporter(ABC):
    @abstractmethod
    def export(self, data): pass

class CSVExporter(Exporter):
    def export(self, data): return 'csv'

class JSONExporter(Exporter):
    def export(self, data): return 'json'

L (Liskov Substitution) — подклассы заменяемы базовым классом

# Правильно: объекты Bird и Fish работают с Animal интерфейсом
class Animal:
    def move(self): pass

class Bird(Animal):
    def move(self): return 'Flying'

class Fish(Animal):
    def move(self): return 'Swimming'

def make_move(animal: Animal):
    return animal.move()

# Работает для любого Animal
make_move(Bird())  # Flying
make_move(Fish())  # Swimming

I (Interface Segregation) — многие специализированные интерфейсы лучше одного общего

# Плохо: один большой интерфейс
class Worker(ABC):
    @abstractmethod
    def work(self): pass
    @abstractmethod
    def eat(self): pass
    @abstractmethod
    def sleep(self): pass

# Хорошо: разные интерфейсы
class Workable(ABC):
    @abstractmethod
    def work(self): pass

class Eatable(ABC):
    @abstractmethod
    def eat(self): pass

D (Dependency Inversion) — зависеть от абстракций, не от конкретных реализаций

# ❌ Плохо: прямая зависимость
class UserService:
    def __init__(self):
        self.db = PostgreSQLDatabase()  # Жёсткая зависимость

# ✅ Хорошо: инъекция зависимостей
class UserService:
    def __init__(self, database):  # Принимаем любую БД
        self.db = database

# Использование
service = UserService(PostgreSQLDatabase())
service = UserService(MockDatabase())  # Для тестирования

Когда использовать ООП в Data Engineering

СлучайПодход
ETL пайплайныАбстрактные базовые классы для Extract/Transform/Load
Обработчики данныхПолиморфизм для разных форматов (CSV, JSON, Parquet)
КонфигурацияКлассы конфигурации с наследованием
ТестированиеMock объекты через наследование

Практический пример для Data Engineer

from abc import ABC, abstractmethod

class DataProcessor(ABC):
    def __init__(self, name):
        self.name = name
    
    @abstractmethod
    def process(self, data):
        pass

class CSVProcessor(DataProcessor):
    def process(self, data):
        return f'Processing CSV from {self.name}'

class JSONProcessor(DataProcessor):
    def process(self, data):
        return f'Processing JSON from {self.name}'

class Pipeline:
    def __init__(self, processor: DataProcessor):
        self.processor = processor
    
    def run(self, data):
        return self.processor.process(data)

# Использование
pipeline_csv = Pipeline(CSVProcessor('source1'))
pipeline_json = Pipeline(JSONProcessor('source2'))

print(pipeline_csv.run('data'))  # Processing CSV from source1
print(pipeline_json.run('data'))  # Processing JSON from source2

ООП позволяет писать масштабируемый, переиспользуемый и тестируемый код — критично для Data Engineering.