Ко всем темам
Тематический маршрут

Python для ML-собеседований

Какие собеседования показывают реальную глубину Python, а не только синтаксис?

Устройство языка, протоколы Python, структуры данных, параллельность и надёжный прикладной код из технических интервью.

3 реальных собеседования13 вопросов6 подтем

Маршрут из трёх собеседований

Проходите по порядку: база → глубина → применение.

Начать отсюдаавгуст 2025 г.

Собирает основу темы и задаёт правильную структуру ответа.

Navio: Алгоритмическая секция

Navio

Широкое интервью по протоколам Python: generators, context managers, dataclasses, descriptors и decorators.

Middle / Senior Аудио · 82 мин 5 ключевых этапов 8 шагов практики

После прохождения

  • Повторить ключевые протоколы языка
  • Отвечать на follow-up о ресурсах, объектах и декораторах
Среда выполнения и модель данныхКоллекции и сложностьИтераторы и протоколыИнструменты языка
Углубитьсямарт 2026 г.

Добавляет сложные follow-up и проверяет глубину понимания.

Sber / GigaChat: Алгоритмическая секция

Sber / GigaChat

Углублённый runtime-блок с hashing, float, GIL, GC и оценкой сложности операций.

Middle / Senior Аудио · 70 мин 5 ключевых этапов 8 шагов практики

После прохождения

  • Объяснять устройство Python за пределами синтаксиса
  • Оценивать сложность и поведение кода на граничных случаях
Среда выполнения и модель данныхКоллекции и сложностьПараллельность
Закрепить на практикефевраль 2026 г.

Переводит знания в coding или законченный прикладной кейс.

HeadHunter Technical: Python, ретраи и надежность

HeadHunter

Практический Python через retry-декоратор, moving average, backoff, cleanup и эксплуатационные метрики.

Middle Аудио · 76 мин 4 ключевых этапа 6 шагов практики

После прохождения

  • Написать устойчивый retry-механизм
  • Проговорить поведение системы при ошибках и деградации
Инструменты языкаНадёжный код

Что покрывает каждое интервью

Матрица построена по конкретным вопросам и этапам, а не по совпадению слов в заголовке.

ПодтемаНачать отсюдаУглубитьсяЗакрепить на практике
Среда выполнения и модель данныхСсылки, сборка мусора, числа и изменяемые значения по умолчанию.Рабочее пониманиеГлубоко
Коллекции и сложностьСписки, множества, хеширование и оценка памяти.ОбзорноГлубоко
Итераторы и протоколыПротокол итератора, генераторы и дескрипторы.Глубоко
ПараллельностьGIL, потоки, процессы и обмен данными.Глубоко
Инструменты языкаДекораторы, контекстные менеджеры и классы данных.ГлубокоРабочее понимание
Надёжный кодПовторные попытки, задержки между ними, очистка ресурсов и наблюдаемость.Глубоко

Отдельная практика по теме

Раскрывайте вопросы и сверяйте ответы прямо здесь. Задачи открываются отдельно в редакторе.

Ключевые вопросы маршрута

Формулировка

Does Python int overflow? How can you roughly estimate how much memory n! needs without computing the factorial?

Короткий ответ

Python ints are arbitrary precision, so they do not overflow like fixed 64-bit ints. Memory grows with bit length, roughly log2(n!), which can be estimated by Stirling.

Python int is arbitrary precision. Arithmetic does not overflow at 32 or 64 bits; instead, the object allocates more machine words as the value grows. Eventually you can still run out of memory or time.

To estimate memory for n!, estimate the number of bits rather than the numeric value itself. The bit length is floor(log2(n!)) + 1. Using Stirling, log2(n!) is approximately n log2(n) - n log2(e) plus a smaller term from sqrt(2 pi n). Divide by 8 for bytes and remember that Python object overhead means the actual memory is higher.

This is the key reasoning move in the interview: switch from the magnitude of n! to the logarithm of n!, because storage is proportional to digits or bits.

Полная карточка: теория, ошибки и прогресс

Формулировка

Что происходит внутри Python for-loop, чем iterator отличается от generator и зачем нужен StopIteration?

Короткий ответ

for-loop вызывает iter(obj), затем next(iterator), пока iterator не поднимет StopIteration. Генератор - это удобный способ создать iterator через функцию с yield.

В for-loop Python сначала получает iterator через iter(obj). Потом он многократно вызывает next(iterator). Когда значения закончились, iterator поднимает StopIteration, и цикл завершается нормально.

Iterator - это объект с протоколом __iter__ и __next__: __iter__ возвращает iterator, а __next__ возвращает следующее значение или поднимает StopIteration. Generator - частный и очень удобный способ создать iterator: функция с yield приостанавливается и сохраняет локальное состояние между вызовами next.

Для обычного ответа на собеседовании этого достаточно: iter, next, StopIteration и yield. Методы generator вроде send/throw/close можно упомянуть только как дополнительный факт, если интервьюер уводит вопрос глубже.

Полная карточка: теория, ошибки и прогресс

Формулировка

What is a Python context manager, what do __enter__ and __exit__ do, and why not just wait for garbage collection?

Короткий ответ

A context manager wraps setup and cleanup around a block. __enter__ prepares the resource, __exit__ runs even on exceptions, and this is safer than waiting for GC to close scarce OS resources.

The with statement is syntax for entering and leaving a managed context. Python calls __enter__ before the block and __exit__ after the block, including when the block exits through an exception. Files, locks, transactions, sockets and temporary settings are common examples.

The reason is deterministic cleanup. Garbage collection is not a good resource-management policy for file descriptors, sockets, locks or database connections. These are external system resources with limits and side effects. Waiting until an object is eventually collected can keep handles open too long, leak locks, hold transactions or exhaust descriptors.

In ML code, the same pattern appears in things such as torch.no_grad or temporary precision/autocast contexts: the context manager makes state changes scoped and reversible.

Полная карточка: теория, ошибки и прогресс

Формулировка

How does @dataclass reduce boilerplate, what does frozen=True do, and how do descriptors or properties relate to attribute access?

Короткий ответ

@dataclass generates boilerplate such as __init__, __repr__ and comparisons from annotations. frozen=True prevents normal assignment after initialization. Descriptors customize attribute get/set/delete behavior.

A dataclass is a class decorator that reads annotated fields and generates common methods, usually including __init__, __repr__ and equality methods. It is a convenience layer over an ordinary Python class, not a new object model.

With frozen=True, the generated class blocks normal attribute reassignment after construction. It is useful for value objects such as points or configuration records. It is not the same as deep immutability: mutable values stored inside can still mutate unless you choose immutable field types.

Descriptors are objects that define methods such as __get__, __set__ and __delete__. Python's attribute access machinery calls them to customize reads and writes. property is a common descriptor-based API. The connection to the interview is that high-level conveniences such as dataclasses and properties ultimately sit on top of Python's attribute model.

Полная карточка: теория, ошибки и прогресс

Формулировка

Как написать декоратор для логирования вызова, зачем нужен functools.wraps и как лениво импортировать модуль внутри wrapper-а?

Короткий ответ

Декоратор принимает функцию и возвращает wrapper. wrapper делает дополнительную работу, вызывает исходную функцию и возвращает результат. functools.wraps сохраняет имя, docstring и __wrapped__, а ленивый импорт можно сделать через importlib.import_module внутри wrapper-а.

Базовый декоратор принимает функцию и возвращает другой callable. Внутренний wrapper принимает *args и **kwargs, может залогировать вход, вызвать исходную функцию, залогировать результат и вернуть его.

functools.wraps(func) нужен, чтобы wrapper не ломал метаданные: __name__, __doc__, annotations и __wrapped__. Это важно для отладки, introspection, тестов и инструментов.

Если у декоратора есть аргументы, появляется один дополнительный уровень функции: factory получает конфигурацию, затем decorator получает исходную функцию, затем wrapper получает runtime-аргументы. Ленивый импорт можно делать внутри wrapper-а через importlib.import_module(name), когда модуль реально нужен; при необходимости модуль кешируют в closure. Важно понимать: ошибка импорта тогда случится во время вызова, а не при старте программы.

Полная карточка: теория, ошибки и прогресс

Формулировка

За сколько работает добавление элемента в начало и в конец Python list? Почему append в конец обычно O(1), но не всегда строго O(1)?

Короткий ответ

Вставка в начало list стоит O(n), потому что элементы нужно сдвинуть. append в конец - амортизированно O(1), но расширение внутреннего буфера иногда стоит O(n).

Python list в CPython реализован как динамический массив ссылок. Добавление в начало требует освободить позицию 0 и сдвинуть все существующие элементы, поэтому это O(n).

Добавление в конец обычно кладет ссылку в уже выделенную свободную ячейку, поэтому стоит O(1). Когда capacity заканчивается, list выделяет новый больший буфер и копирует туда старые ссылки. Такой отдельный append стоит O(n), но средняя стоимость по длинной серии append остается амортизированно O(1).

Полная карточка: теория, ошибки и прогресс

Формулировка

За сколько вставить n различных строк длины k в Python set? Что изменится, если hash для всех объектов возвращает одно и то же значение?

Короткий ответ

Обычно получается O(n * k): hash строки считается по символам, а вставка в hash table амортизированно O(1). При константном hash серия вставок деградирует к квадратичной.

Для обычных строк Python должен вычислить hash по символам, поэтому первая вставка строки длины k включает O(k) на хеширование. Сама операция set.add в среднем амортизированно O(1), если hash хорошо распределяет элементы.

Если сделать hash константным, все элементы попадают в один кластер коллизий. Тогда при вставке нового элемента нужно проверять уже лежащие элементы с тем же hash, и суммарно по n вставкам получается квадратичная деградация. Для строк и пользовательских объектов при коллизиях также важна стоимость equality-сравнений.

Полная карточка: теория, ошибки и прогресс

Формулировка

Как устроены числа с плавающей точкой? Чем bfloat16 отличается от float16 и почему его используют в нейросетях?

Короткий ответ

Float хранит знак, экспоненту и мантиссу. bfloat16 экономит память как 16-битный формат, но сохраняет широкий диапазон FP32 за счет 8-битной экспоненты.

Число с плавающей точкой представляется как sign, exponent и significand/mantissa. Экспонента задает масштаб, мантисса - значащие биты. FP32 имеет 1 бит знака, 8 бит экспоненты и 23 бита мантиссы.

bfloat16 использует 16 бит, но оставляет экспоненту шириной как у FP32: 1 бит знака, 8 бит экспоненты и 7 бит мантиссы. Он менее точен по мантиссе, зато лучше переносит широкий диапазон активаций и градиентов. В ML это удобно для обучения и инференса: меньше память и выше throughput, при этом меньше проблем с overflow/underflow, чем у FP16.

Полная карточка: теория, ошибки и прогресс

Формулировка

Чем отличаются потоки и процессы? Что такое GIL в CPython, когда нужны синхронизация и IPC?

Короткий ответ

Процессы имеют отдельную память, потоки внутри процесса делят память. GIL ограничивает одновременное исполнение Python bytecode, но не отменяет race conditions и синхронизацию.

Процесс - более тяжелая единица выполнения с отдельным адресным пространством. Поток легче: он живет внутри процесса и разделяет память, файловые дескрипторы и состояние процесса, но имеет свой stack.

GIL в CPython - глобальная блокировка, которую должен держать поток, исполняющий Python bytecode. Из-за этого CPU-bound Python-код обычно не ускоряется на нескольких потоках. Потоки полезны для I/O-bound задач, потому что GIL может отпускаться на ожидании I/O и в некоторых C-расширениях.

GIL не делает бизнес-инварианты атомарными. Для shared mutable state нужны Lock, Queue или другие примитивы синхронизации. Между процессами нельзя передать обычную Python-ссылку: нужны pipes, queues, sockets, shared memory, файлы, Redis или другой IPC/transport.

Полная карточка: теория, ошибки и прогресс

Формулировка

В Python есть код со списками, ссылками на объекты, циклическими ссылками и mutable default arguments. Как пройтись по нему и объяснить, что останется в памяти и почему?

Короткий ответ

В CPython обычные объекты освобождаются reference counting, но циклы не разбираются одним счетчиком ссылок. Для недостижимых циклов нужен cyclic GC. Mutable default argument создается один раз при определении функции, поэтому список переиспользуется между вызовами без явного аргумента.

В CPython у каждого объекта есть счетчик ссылок. Когда счетчик падает до нуля, объект можно сразу освободить. Это хорошо работает для обычных ацикличных графов объектов.

Проблема возникает, когда объекты ссылаются друг на друга: например, список a содержит ссылку на b, а b содержит ссылку на a. Если внешние переменные перестали указывать на этот цикл, счетчики ссылок внутри цикла все равно не равны нулю, потому что объекты держат друг друга. Reference counting сам по себе такой цикл не освободит.

Для этого в CPython есть cyclic garbage collector. Он периодически ищет группы контейнерных объектов, которые достижимы только друг из друга, но недостижимы из корней программы. Такие циклы можно собрать. При этом освобождение не обязано случиться ровно в момент зануления внешней ссылки.

Mutable default argument — другой классический runtime-подвох. Значение аргумента по умолчанию вычисляется один раз при создании функции, а не при каждом вызове. Поэтому функция вида def f(x=[]): x.append(1); return x будет переиспользовать один и тот же список во всех вызовах без явного x. Правильный паттерн — использовать None как sentinel и создавать новый список внутри функции.

Полная карточка: теория, ошибки и прогресс

На страницах собеседований есть ещё 3 связанных вопросов.

Задачи из этих интервью

Повторить теорию перед практикой