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

Ответ

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 реализованы гибко, что позволяет писать выразительный и эффективный объектно-ориентированный код.

Ответ 18+ 🔞

А, ну вот, опять про эти ваши объекты ориентации. Слушай, давай я тебе на пальцах объясню, как это в Питоне работает, а то в учебниках пишут такое, что волнение ебать, терпения ноль ебать.

1. Инкапсуляция, или "Спрячь своё говно" Это когда ты делаешь вид, что у тебя внутри всё чисто и красиво, а на самом деле там бардак, но ты его прикрыл. В Питоне это вообще ебушки-воробушки — строгости ноль, всё на доверии. Один подчёркивание — типа "не лезь, а то обидно будет". Два подчёркивания — "ну ты ваще охренел что ли, зачем тебе туда?" Но если очень хочется — залезешь, конечно.

class ДатчикТемпературы:
    def __init__(self):
        self._текущая_температура = 0.0  # Ну типа не трогай, ладно?
        self.__коэффициент_поправки = 1.02  # Вообще отъебись, это приватное!

    def дай_температуру(self):
        # А внутри-то какая-то магия, калибровка, херня
        сырое_значение = self._прочитай_железку()
        self._текущая_температура = сырое_значение * self.__коэффициент_поправки
        return self._текущая_температура

    def _прочитай_железку(self):
        return 25.5  # Просто с потолка взял, да похуй

датчик = ДатчикТемпературы()
print(датчик.дай_температуру())  # Всё ок, работает
# print(датчик.__коэффициент_поправки) # А вот тут тебе AttributeError в морду
# Но если упоротый — print(датчик._ДатчикТемпературы__коэффициент_поправки) — и всё, ты в душу бога мать полез.

2. Наследование, или "Папины гены" Это когда ты берёшь чужой класс и говоришь: "Всё твоё теперь моё, а ещё я тут своё прикручу". В Питоне можно наследоваться хоть от десяти классов сразу — ёперный театр, конечно, но можно.

class Транспорт:
    def __init__(self, марка, модель):
        self.марка = марка
        self.модель = модель
        self._скорость = 0

    def поехали(self):
        return f"{self.марка} {self.модель} поехал."

class Электромобиль(Транспорт):  # Смотри-ка, сынок у папы
    def __init__(self, марка, модель, ёмкость_батареи):
        super().__init__(марка, модель)  # Папины гены подтянули
        self.ёмкость_батареи = ёмкость_батареи
        self._заряд = 100

    # А вот тут сынок говорит: "Папа, твой метод — говно, я сделаю по-своему"
    def поехали(self):
        if self._заряд > 0:
            self._заряд -= 5
            return f"{self.марка} {self.модель} едет тихо. Заряд: {self._заряд}%"
        else:
            return "Батарея сдохла!"

тесла = Электромобиль("Tesla", "Model S", 100)
print(тесла.поехали())  # Работает переопределённый метод
print(isinstance(тесла, Транспорт))  # True, он и правда сынок

3. Полиморфизм, или "Если крякает как утка..." Это вообще любимая фишка Питона. Нам похуй, что это за объект. Главное, чтобы у него был нужный метод. Утка? Селезень? Хуй с горы? Если у него есть метод .export() — значит, он экспортёр, и мы его используем.

class PDFВыгрузчик:
    def export(self, данные):
        return "PDF-файл готов"

class CSVВыгрузчик:
    def export(self, данные):
        return "CSV-файл готов"

class ГенераторОтчётов:
    def сделай_отчёт(self, данные, выгрузчик): # Нам всё равно, что за выгрузчик
        результат = выгрузчик.export(данные)  # Главное, чтобы export был!
        print(результат)

генератор = ГенераторОтчётов()
данные = {"продажи": [100, 200, 150]}
генератор.сделай_отчёт(данные, PDFВыгрузчик())  # PDF-файл готов
генератор.сделай_отчёт(данные, CSVВыгрузчик())  # CSV-файл готов
# Сюда можно хоть ХуйВПальто подсунуть, если у него export() есть — будет работать.

4. Абстракция, или "Обещания, которые надо выполнить" Это когда ты говоришь: "Вот тебе план, детали — твои проблемы". Абстрактный класс — это такой начальник, который говорит "сделай!", но сам нихуя не делает. А ты обязан реализовать.

from abc import ABC, abstractmethod
import numpy as np

class МодельМО(ABC):
    """Ну типа модель машинного обучения, абстрактная такая."""
    @abstractmethod
    def обучи(self, X, y):
        """Обучи модель. Как — твои трудности."""
        pass

    @abstractmethod
    def предскажи(self, X):
        """Сделай предсказание. И чтобы работало."""
        pass

    @property
    @abstractmethod
    def обучена_ли(self):
        pass

class ЛинейнаяРегрессия(МодельМО):
    """А вот тут уже конкретика, без абстракций."""
    def __init__(self):
        self._коэфф = None
        self._пересечение = None
        self._обучена = False

    def обучи(self, X, y):
        # А вот тут реальная, сложная математика, овердохуища формул
        X_с_единицей = np.c_[np.ones(X.shape[0]), X]
        коэффициенты = np.linalg.pinv(X_с_единицей.T @ X_с_единицей) @ X_с_единицей.T @ y
        self._пересечение, *self._коэфф = коэффициенты
        self._обучена = True  # Ура, научилась!

    def предскажи(self, X):
        if not self._обучена:
            raise ValueError("Модель ещё не обучена, иди учи!")
        return self._пересечение + X @ self._коэфф

    @property
    def обучена_ли(self):
        return self._обучена

# А пользователь вообще в душе не ебёт, как там внутри. Ему главное — predict.
модель = ЛинейнаяРегрессия()
модель.обучи(X_тренировка, y_тренировка)
предсказания = модель.предскажи(X_тест)

Вот и вся магия. В Питоне это не железные правила, а так, рекомендации. Хочешь — соблюдай, хочешь — делай как знаешь. Но если хочешь, чтобы твой код не выглядел как пиздопроебибна, лучше придерживайся.