ООП
ООП в Python выглядит знакомо — class, наследование, методы, — но почти каждый механизм под капотом устроен иначе, чем в C++ или Java, и собеседование бьёт ровно в эту разницу. Приватности здесь нет: есть соглашение _x и искажение имён __x в _Class__x. Интерфейсов как отдельной сущности нет: есть утиная типизация, abc.ABC для номинальной проверки и typing.Protocol для структурной. Перегрузки методов по сигнатуре нет: тело класса — это обычное пространство имён, и второе определение просто затирает первое. А атрибуты экземпляра лежат в обыкновенном dict, поэтому объекту можно приписать что угодно на ходу — пока класс не объявит __slots__.
Вторая особенность — всё поведение объекта задаётся протоколами, а не наследованием от базового типа. Длину даёт __len__, сравнение — __eq__, срезы — __getitem__, блок with — пара __enter__/__exit__. Интерпретатор ищет эти методы на типе, а не на экземпляре, и связывает их с синтаксисом. Отсюда растёт и третья часть темы: принципы SOLID и шаблоны GoF в Python остаются в силе, но половина из них схлопывается — функция первого класса заменяет класс-стратегию, модуль заменяет одиночку, functools.singledispatch заменяет посетителя. Умение сказать, где шаблон нужен, а где он лишний в языке с первоклассными функциями, отличает middle от кандидата, выучившего каталог наизусть.
Карта темы
- Четыре столпа ООП — инкапсуляция, наследование, полиморфизм, абстракция и то, во что каждый из них превращается в Python.
- Абстракция — выделение контракта без деталей реализации и её отличие от инкапсуляции.
- Инкапсуляция — соглашения
_x/__x,propertyвместо геттеров и почему сокрытия данных нет. - Наследование и super() — переопределение методов, формы
super()и цена жёсткого вызова базового класса по имени. - Полиморфизм — переопределение, перегрузка операторов,
singledispatchи почему перегрузки по сигнатуре нет. - Утиная типизация — проверка по поведению вместо типа, EAFP и
Protocolпротивabc.ABC. - MRO и C3-линеаризация — порядок разрешения методов, правила C3 и почему некоторые иерархии не компилируются.
- Множественное наследование — ромб, кооперативный
super()и передача аргументов по цепочке. - Примеси (mixins) — маленький класс с поведением без состояния и почему порядок баз критичен.
- Словарь экземпляра и __slots__ — где живут атрибуты, затенение атрибута класса и что даёт
__slots__. - Магические методы — протоколы вместо интерфейсов, поиск dunder на типе,
__new__против__init__. - Равенство и идентичность —
isпротив==, контракт__eq__/__hash__иNotImplemented. - Искажение имён —
__xпревращается в_Class__x; это защита от коллизий, а не приватность. - Менеджеры контекста —
__enter__/__exit__, подавление исключения возвратомTrueиcontextlib. - Абстрактные базовые классы —
abc.ABC,@abstractmethod, запрет создания экземпляра иProtocolкак альтернатива. - SOLID — пять принципов и как они выглядят в языке с утиной типизацией.
- Связность и связанность — cohesion внутри модуля и coupling между модулями, и почему их путают.
- Композиция вместо наследования — делегирование против иерархии и ловушка наследования от
list. - DRY, KISS, YAGNI, SLAP — четыре принципа повседневного кода и их типичные перегибы.
- Шаблоны проектирования — три категории GoF, зачем нужен общий словарь и когда шаблон лишний.
- Порождающие шаблоны — фабричный метод, абстрактная фабрика, строитель, прототип и их питонические замены.
- Структурные шаблоны — адаптер, декоратор, фасад, заместитель, компоновщик, мост, приспособленец.
- Поведенческие шаблоны — стратегия, наблюдатель, команда, шаблонный метод, состояние, цепочка обязанностей и остальные.
- Одиночка (Singleton) — через
__new__, через метакласс, и почему в Python обычно достаточно модуля.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Называть __x приватным полем | Это лишь искажение имени в _Class__x — атрибут доступен снаружи, защита только от коллизий в иерархии |
| Изменяемый атрибут класса вместо атрибута экземпляра | Список или словарь общий для всех экземпляров; self.items.append(x) виден во всех объектах сразу |
Забыть super().__init__() в переопределённом __init__ | Базовый класс не инициализирован, его атрибуты отсутствуют, падение с AttributeError уже при первом обращении |
Определить __eq__ без __hash__ | Python выставляет __hash__ = None, объект становится нехешируемым и не кладётся в set или ключ dict |
Считать, что super() вызывает родителя | super() идёт по MRO типа экземпляра — в ромбе он попадает в соседнюю ветку, а не в базовый класс |
Вернуть из __exit__ истинное значение по невнимательности | Исключение молча подавляется, ошибка исчезает из логов, а блок with выглядит успешным |
Наследоваться от list/dict, чтобы «добавить поведение» | Встроенные методы зовут друг друга внутри C-кода мимо ваших переопределений — часть операций проходит незамеченной |
| Тащить каталог GoF в Python дословно | Стратегия, команда и посетитель превращаются в лишние классы там, где хватает функции, partial или singledispatch |
Значение для собеседований
ООП спрашивают на любом уровне, но глубина разная. Junior обязан назвать четыре столпа, объяснить super(), показать, где хранятся атрибуты, и написать класс с __init__ и __repr__. Middle получает вопросы про MRO и ромб («что напечатает этот код»), про __slots__, про __eq__ и __hash__ вместе, про менеджер контекста руками и про разницу abc.ABC и Protocol. Отдельный блок — шаблоны проектирования: у них спрашивают не определение, а задачу, которую шаблон решает, и почти всегда добавляют «а как это делают в Python». Ответ «через функцию» здесь чаще правильный, чем «через класс».
Заваливают тему тремя способами. Первый — пересказ ООП из Java: «приватные поля», «интерфейсы», «перегрузка методов». Ни одного из трёх понятий в Python нет в том виде, и интервьюер сразу проверит это вопросом про __x или про два def с одним именем. Второй — определение вместо механизма: «полиморфизм — это когда объекты разных классов ведут себя по-разному» без единого слова про поиск метода по MRO и про dunder-протоколы. Третий — шаблон ради шаблона: рукописный Singleton через метакласс там, где модуль уже одиночка, или иерархия из пяти классов-стратегий вместо словаря функций. Готовьтесь так, чтобы на каждый термин у вас был кусок кода на десять строк и один названный вслух подводный камень.