Как реализуются принципы ООП в Python?

«Как реализуются принципы ООП в Python?» — вопрос из категории ООП, который задают на 26% собеседований Data Scientist / ML Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

1. Инкапсуляция (Encapsulation) Сокрытие внутреннего состояния объекта и предоставление контролируемого доступа через публичные методы. В Python это соглашение, а не строгое правило.

class TemperatureSensor:
    def __init__(self):
        # Защищенный атрибут (соглашение: один underscore)
        self._current_temperature = 0.0
        # Приватный атрибут (name mangling: два underscore)
        self.__calibration_factor = 1.02

    # Публичный метод-геттер для контролируемого доступа
    def get_temperature(self):
        # Внутренняя логика, например, чтение с датчика и калибровка
        raw_reading = self._read_hardware()
        self._current_temperature = raw_reading * self.__calibration_factor
        return self._current_temperature

    # Защищенный метод для внутреннего использования
    def _read_hardware(self):
        # Симуляция чтения с аппаратного датчика
        return 25.5

    # Публичный метод-сеттер с валидацией
    def set_calibration(self, factor):
        if 0.5 < factor < 1.5:
            self.__calibration_factor = factor
        else:
            raise ValueError("Calibration factor out of range")

sensor = TemperatureSensor()
print(sensor.get_temperature())  # OK: 26.01
# print(sensor.__calibration_factor) # AttributeError
print(sensor._TemperatureSensor__calibration_factor) # Доступ с name mangling (но не нужно так делать)
sensor.set_calibration(1.1) # OK

2. Наследование (Inheritance) Создание нового класса на основе существующего с возможностью переопределения или расширения функциональности. Python поддерживает множественное наследование.

class Vehicle:
    def __init__(self, make, model):
        self.make = make
        self.model = model
        self._speed = 0

    def move(self):
        return f"The {self.make} {self.model} is moving."

class ElectricCar(Vehicle):  # ElectricCar наследует от Vehicle
    def __init__(self, make, model, battery_capacity):
        # Вызов конструктора родительского класса
        super().__init__(make, model)
        self.battery_capacity = battery_capacity
        self._charge_level = 100

    # Переопределение метода родителя
    def move(self):
        if self._charge_level > 0:
            self._charge_level -= 5
            return f"The {self.make} {self.model} moves silently. Charge: {self._charge_level}%"
        else:
            return "Battery depleted!"

    # Новый метод, специфичный для дочернего класса
    def recharge(self):
        self._charge_level = 100

my_tesla = ElectricCar("Tesla", "Model S", 100)
print(my_tesla.move())  # Вызов переопределенного метода
print(isinstance(my_tesla, Vehicle))  # True
print(issubclass(ElectricCar, Vehicle))  # True

3. Полиморфизм (Polymorphism) Возможность объектов с разной внутренней структурой иметь одинаковый интерфейс. В Python это реализуется через утиную типизацию (duck typing).

class PDFExporter:
    def export(self, data):
        # Логика генерации PDF
        return "PDF file generated"

class CSVExporter:
    def export(self, data):
        # Логика генерации CSV
        return "CSV file generated"

class ReportGenerator:
    def generate_report(self, data, exporter): # exporter — любой объект с методом export()
        # Полиморфный вызов: не важно, какой именно экспортер,
        # главное, что у него есть метод export.
        result = exporter.export(data)
        print(result)

# Использование
generator = ReportGenerator()
data = {"sales": [100, 200, 150]}
generator.generate_report(data, PDFExporter())  # Выведет: PDF file generated
generator.generate_report(data, CSVExporter())  # Выведет: CSV file generated

4. Абстракция (Abstraction) Сокрытие сложной реализации и предоставление простого интерфейса. Достигается через абстрактные базовые классы (ABC).

from abc import ABC, abstractmethod
import numpy as np

class MLModel(ABC):
    """Абстракция модели машинного обучения."""
    @abstractmethod
    def fit(self, X, y):
        """Обучить модель на данных X, y."""
        pass

    @abstractmethod
    def predict(self, X):
        """Сделать предсказание для данных X."""
        pass

    # Абстрактное свойство
    @property
    @abstractmethod
    def is_fitted(self):
        pass

class LinearRegressionModel(MLModel):
    """Конкретная реализация абстракции."""
    def __init__(self):
        self._coef = None
        self._intercept = None
        self._fitted = False

    def fit(self, X, y):
        # Реальная, сложная реализация метода fit
        X_with_intercept = np.c_[np.ones(X.shape[0]), X]
        coeffs = np.linalg.pinv(X_with_intercept.T @ X_with_intercept) @ X_with_intercept.T @ y
        self._intercept, *self._coef = coeffs
        self._fitted = True

    def predict(self, X):
        if not self._fitted:
            raise ValueError("Model is not fitted yet.")
        return self._intercept + X @ self._coef

    @property
    def is_fitted(self):
        return self._fitted

# Пользователь работает с абстракцией, не зная деталей реализации.
model = LinearRegressionModel()
model.fit(X_train, y_train)
predictions = model.predict(X_test)

Эти принципы в Python реализованы гибко, что позволяет писать выразительный и эффективный объектно-ориентированный код.