ООП в Python — классы и объектно-ориентированное программирование для аналитика
Что такое ООП
Объектно-ориентированное программирование — подход, при котором данные и функции для работы с ними объединяются в одну сущность — объект. Объект создаётся по шаблону, который называется классом. В Python всё является объектом: строки, списки, даже функции. Но чтобы создавать свои объекты, нужно разобраться с классами.
Для аналитиков ООП — не ежедневный инструмент. Большую часть задач можно решить функциями и pandas. Но на собеседованиях тему спрашивают, а в реальной работе классы полезны для пайплайнов, кастомных трансформеров и структурирования кода.
Синтаксис классов
Класс — это чертёж объекта. Метод __init__ вызывается при создании экземпляра и задаёт начальное состояние. self — ссылка на текущий экземпляр.
class Metric:
def __init__(self, name, values):
self.name = name
self.values = values
def mean(self):
return sum(self.values) / len(self.values)
def describe(self):
return f"{self.name}: mean={self.mean():.2f}, n={len(self.values)}"
revenue = Metric("Revenue", [100, 150, 130, 170, 160])
print(revenue.mean()) # 142.0
print(revenue.describe()) # Revenue: mean=142.00, n=5self.name и self.values — атрибуты экземпляра. Каждый объект хранит свои данные независимо. mean() и describe() — методы: функции, привязанные к объекту.
init и self подробнее
__init__ — не конструктор (конструктор — это __new__), а инициализатор. Он получает уже созданный объект и заполняет его атрибутами.
self — не ключевое слово, а конвенция. Технически можно назвать как угодно, но self — стандарт, и отступать от него не стоит.
class ABTest:
def __init__(self, control, treatment, metric_name="conversion"):
self.control = control
self.treatment = treatment
self.metric_name = metric_name
self.is_significant = None # заполним позже
def run(self, alpha=0.05):
from scipy import stats
stat, p_value = stats.mannwhitneyu(self.control, self.treatment)
self.is_significant = p_value < alpha
return p_valueЗдесь __init__ принимает данные двух групп и название метрики. Метод run() выполняет тест и сохраняет результат в атрибут is_significant.
Наследование
Наследование позволяет создать новый класс на основе существующего, переиспользуя и расширяя его логику.
class BaseTransformer:
def __init__(self, column):
self.column = column
def fit(self, df):
return self
def transform(self, df):
raise NotImplementedError("Переопредели в подклассе")
class FillNaTransformer(BaseTransformer):
def __init__(self, column, fill_value=0):
super().__init__(column) # вызываем __init__ родителя
self.fill_value = fill_value
def transform(self, df):
df = df.copy()
df[self.column] = df[self.column].fillna(self.fill_value)
return df
class LogTransformer(BaseTransformer):
def transform(self, df):
import numpy as np
df = df.copy()
df[self.column] = np.log1p(df[self.column])
return dfsuper().__init__(column) вызывает инициализатор родительского класса. Оба трансформера имеют единый интерфейс (fit / transform), что позволяет использовать их в пайплайне.
Когда аналитику нужен ООП
Пайплайны обработки данных. Когда трансформации выстраиваются в цепочку, классы дают единый интерфейс.
class Pipeline:
def __init__(self, steps):
self.steps = steps
def run(self, df):
for step in self.steps:
df = step.transform(df)
return df
pipe = Pipeline([
FillNaTransformer("revenue", fill_value=0),
LogTransformer("revenue"),
])
result = pipe.run(raw_df)Кастомные трансформеры для sklearn. Если вы строите ML-модели, sklearn ожидает объекты с методами fit() и transform().
Хранение конфигурации. Класс удобно группирует связанные параметры и методы — вместо разрозненных переменных и функций.
Инкапсуляция: публичное и приватное
Python не имеет настоящего private, но есть конвенции:
class User:
def __init__(self, name, salary):
self.name = name # публичный атрибут
self._salary = salary # «приватный» (по конвенции)
self.__bonus = 0 # name mangling: User__bonus
def total_comp(self):
return self._salary + self.__bonusОдно подчёркивание _salary — «не трогайте снаружи, но технически можно». Двойное __bonus — срабатывает name mangling, доступ через _User__bonus. На практике одного подчёркивания достаточно.
dataclass — классы без шаблонного кода
Начиная с Python 3.7, dataclass автоматически генерирует __init__, __repr__, __eq__ и другие методы:
from dataclasses import dataclass
@dataclass
class ExperimentConfig:
name: str
control_group: str
treatment_group: str
metric: str
alpha: float = 0.05
min_sample_size: int = 1000
config = ExperimentConfig(
name="pricing_test",
control_group="A",
treatment_group="B",
metric="revenue_per_user",
)
print(config)
# ExperimentConfig(name='pricing_test', control_group='A',
# treatment_group='B', metric='revenue_per_user',
# alpha=0.05, min_sample_size=1000)dataclass идеален для хранения структурированных данных — конфигураций, результатов экспериментов, метаданных. Не нужно вручную писать __init__ и __repr__. Подробнее про типы данных Python.
Магические методы
Методы вида __xxx__ (dunder-методы) позволяют определить поведение объекта с операторами и встроенными функциями:
@dataclass
class MetricResult:
name: str
value: float
def __str__(self):
return f"{self.name}: {self.value:.4f}"
def __lt__(self, other):
return self.value < other.value
results = [
MetricResult("CR", 0.032),
MetricResult("ARPU", 156.5),
MetricResult("Retention D7", 0.41),
]
print(sorted(results)) # сортировка работает через __lt__Основные: __str__ (для print), __repr__ (для отладки), __len__, __getitem__, __eq__, __lt__.
Вопросы с собеседований
— Что такое self в Python?
— self — ссылка на текущий экземпляр класса. Через неё метод получает доступ к атрибутам и другим методам объекта. При вызове obj.method() Python автоматически передаёт obj как первый аргумент (self).
— Чем отличается класс от экземпляра?
— Класс — шаблон (чертёж), экземпляр — конкретный объект, созданный по этому шаблону. Класс User один, а экземпляров User("Анна"), User("Борис") может быть сколько угодно. У каждого экземпляра свои значения атрибутов.
— Что делает super()?
— super() возвращает объект-прокси родительского класса. Используется, чтобы вызвать метод родителя из дочернего класса — чаще всего super().__init__() в инициализаторе, чтобы не дублировать логику.
— Что такое dataclass и когда его использовать?
— dataclass (из модуля dataclasses, Python 3.7+) автоматически генерирует __init__, __repr__, __eq__ по аннотациям типов. Используют для классов, которые в основном хранят данные — конфиги, DTO, результаты. Экономит шаблонный код.
— Что такое множественное наследование и какие проблемы оно создаёт?
— Класс может наследовать от нескольких родителей: class C(A, B). Проблема — diamond problem: если A и B наследуют от общего предка и переопределяют один метод, непонятно, какую версию использовать. Python решает это через MRO (Method Resolution Order) — линеаризацию C3.
FAQ
Нужен ли ООП для работы аналитиком?
Для ежедневной работы с данными — нет, хватит функций, pandas и SQL. Но понимание ООП важно: вы читаете код библиотек (pandas DataFrame — это класс), пишете кастомные трансформеры для sklearn, структурируете пайплайны. На собеседованиях тема встречается на middle+ уровне. Базовые знания — class, init, наследование — нужны всем.
Чем dataclass отличается от обычного класса?
dataclass — обычный класс с автоматически сгенерированными методами. Декоратор @dataclass создаёт __init__, __repr__, __eq__ из аннотаций типов. Можно добавлять свои методы. Разница — только в удобстве: меньше шаблонного кода. Для сложной логики используйте обычные классы, для хранения данных — dataclass.
Как выбрать между функцией и классом?
Если логика — одно действие без состояния (посчитать метрику, преобразовать столбец) — используйте функцию. Если нужно хранить состояние между вызовами, объединить несколько связанных операций или обеспечить единый интерфейс для разных реализаций — класс. Подробнее про функциональный подход — в гайде по Python для аналитика.
Потренируйте вопросы по Python на реальных задачах — откройте тренажёр. 1500+ вопросов, которые спрашивают на собеседованиях аналитика. Бесплатно.