Урок по треку Python 8 мин

ООП в Python: классы, MRO, магические методы и dataclasses

Классы, наследование и MRO, super, магические методы, property, дескрипторы, __slots__, dataclasses и метаклассы.

ООП в Python устроено проще, чем в Java: нет модификаторов доступа, интерфейсы заменяет утиная типизация, а почти всё поведение объекта настраивается магическими методами. Именно про них и про MRO любят спрашивать.

Класс, экземпляр, атрибуты

class User:
    role = 'guest'              # атрибут класса, общий для всех

    def __init__(self, name):
        self.name = name        # атрибут экземпляра

u = User('Аня')
u.role                          # 'guest': не нашли в экземпляре, нашли в классе
u.role = 'admin'                # создали атрибут экземпляра, класс не изменился

Поиск атрибута: сначала u.__dict__, потом класс и его родители по MRO. Изменяемый атрибут класса (items = []) общий для всех экземпляров: классическая ловушка.

__init__ не конструктор, а инициализатор: объект уже создан методом __new__. __new__ переопределяют редко: для неизменяемых типов (наследник str, tuple) и синглтонов.

Методы: обычный, класса, статический

  • Обычный метод получает экземпляр self.
  • @classmethod получает класс cls; типичное применение: альтернативные конструкторы User.from_dict(data), которые правильно работают в наследниках.
  • @staticmethod ничего не получает, это просто функция в пространстве имён класса.

Инкапсуляция по соглашению

_name значит «внутреннее, не трогайте». __name включает искажение имён (name mangling): внутри класса A оно становится _A__name. Это защита от случайных конфликтов в наследниках, а не приватность.

@property превращает метод в атрибут с вычислением и проверкой, не ломая внешний интерфейс.

Наследование и MRO

Python поддерживает множественное наследование. Порядок поиска методов (MRO) строится алгоритмом C3-линеаризации: потомок раньше предков, порядок родителей из объявления сохраняется.

class A: ...
class B(A): ...
class C(A): ...
class D(B, C): ...
D.__mro__   # D, B, C, A, object

super() вызывает следующий класс в MRO экземпляра, а не обязательно прямого родителя. Поэтому в «ромбе» каждый __init__ с super().__init__() выполнится ровно один раз.

Магические методы

МетодКогда вызывается
__repr__, __str__repr(x) и отладка, str(x) и print
__eq__, __hash__==, ключи словаря
__lt__ и др.сравнения, sorted
__len__, __getitem__, __contains__, __iter__протокол коллекции
__call__вызов объекта как функции
__enter__, __exit__with
__getattr__только если обычный поиск атрибута не нашёл ничего

Если определить __eq__ без __hash__, Python выставит __hash__ = None, и объекты перестанут быть хешируемыми.

Дескрипторы и slots (senior)

Дескриптор это объект с __get__/__set__, лежащий в атрибуте класса. На дескрипторах построены property, classmethod, staticmethod и сами методы (функция это дескриптор, который возвращает связанный метод).

__slots__ = ('x', 'y') убирает __dict__ у экземпляров: память меньше, доступ чуть быстрее, но новые атрибуты добавить нельзя.

dataclasses

from dataclasses import dataclass, field

@dataclass(frozen=True, slots=True)
class Point:
    x: float
    y: float
    tags: list[str] = field(default_factory=list)

Генерирует __init__, __repr__, __eq__; с order=True сравнения; с frozen=True запрещает присваивание и даёт __hash__. Изменяемое значение по умолчанию задают только через default_factory. Для валидации входных данных в вебе обычно берут Pydantic, а dataclass оставляют для внутренних структур.

Метаклассы (senior)

Класс это объект, созданный метаклассом (по умолчанию type). Метакласс управляет созданием классов: регистрация, проверка, автодобавление методов. В современном коде большую часть этих задач решают __init_subclass__ и декораторы классов.

Что спросят на собесе

  • Разница между атрибутом класса и экземпляра, @classmethod и @staticmethod.
  • Что такое MRO, как работает super() при множественном наследовании.
  • Зачем __repr__, связь __eq__ и __hash__.
  • Что делает __slots__, что такое дескриптор и property.
  • Что генерирует @dataclass и почему нельзя tags: list = [].

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.