Вопросы на собеседовании Python-разработчика: 25 вопросов с разбором
Не список определений для заучивания, а разбор того, что за каждым вопросом на самом деле проверяют — и как отвечать, чтобы это было видно.
Списков «100 вопросов по Python» в интернете достаточно, и толку от них мало: заученное определение декоратора слышно сразу. Интервьюер почти всегда задаёт следующий вопрос — «а зачем нужен functools.wraps?» — и на нём заготовка кончается.
Поэтому здесь разбор по темам: что спрашивают, что за этим проверяют и как ответить так, чтобы уточняющий вопрос вы встретили спокойно.
Типы данных и модель памяти
Какие типы изменяемые, какие нет?
Вопрос кажется школьным, но за ним тянется половина остальных.
Неизменяемые: int, float, str, tuple, frozenset, bytes. Изменяемые: list, dict, set, bytearray.
Хороший ответ не останавливается на списке, а сразу показывает следствие: изменяемый объект, переданный в функцию, меняется у вызывающего.
Что не так с этим кодом?
def add_item(item, basket=[]):
basket.append(item)
return basket
Классическая ловушка. Список создаётся один раз — в момент определения функции, а не при каждом вызове. Поэтому второй вызов add_item("b") вернёт ["a", "b"], чего никто не ждал.
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
Если после этого спросят, где такое поведение полезно, — ответ есть: кеширование через изменяемый аргумент по умолчанию иногда пишут намеренно, хотя functools.lru_cache честнее.
Чем is отличается от ==?
== сравнивает значения, is — тождественность объектов в памяти.
Уточняющий вопрос почти всегда про мелкие целые: a = 256; b = 256; a is b даёт True, а с 257 — False. Python кеширует небольшие числа от −5 до 256. Правильная реакция на это — не «надо запомнить», а «поэтому на is сравнивают только с None, True и False».
Как работает копирование?
Присваивание копию не делает — это второе имя для того же объекта. copy.copy() делает поверхностную копию: новый контейнер, но те же вложенные объекты. copy.deepcopy() копирует рекурсивно.
a = [[1, 2], [3, 4]]
b = a.copy()
b[0].append(99)
print(a) # [[1, 2, 99], [3, 4]] — вложенный список общий
Функции и декораторы
Что такое декоратор?
Функция, которая принимает функцию и возвращает функцию. Определение простое, но само по себе оно ничего не показывает — лучше сразу написать.
import functools, time
def timed(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return fn(*args, **kwargs)
finally:
print(f"{fn.__name__}: {time.perf_counter() - start:.3f}s")
return wrapper
Здесь две детали, которые и проверяют. functools.wraps переносит на обёртку имя и докстроку оригинала — без него fn.__name__ станет "wrapper", и это ломает всё, что полагается на интроспекцию, от логов до Sphinx. А try/finally гарантирует, что время замерится даже при исключении.
Что такое замыкание?
Функция, которая помнит переменные из объемлющей области видимости после того, как та завершилась.
def counter():
n = 0
def inc():
nonlocal n # без nonlocal будет UnboundLocalError
n += 1
return n
return inc
nonlocal здесь — то, ради чего вопрос задают.
Чем *args отличается от **kwargs?
Первое собирает позиционные аргументы в кортеж, второе — именованные в словарь. Полезно добавить про звёздочку как разделитель: аргументы после * в сигнатуре можно передать только по имени, и это хороший способ защитить API от путаницы в порядке.
Генераторы и итераторы
Чем генератор отличается от списка?
Генератор вычисляет элементы по одному и не хранит их все в памяти. Списковое включение на миллион элементов займёт память под миллион, генераторное — под один.
sum(x * x for x in range(10_000_000)) # память постоянная
sum([x * x for x in range(10_000_000)]) # ~400 МБ
Цена — по генератору нельзя пройти дважды и нельзя взять len().
Что такое протокол итератора?
Объект итерируем, если у него есть __iter__, возвращающий итератор. Итератор — объект с __next__, который отдаёт следующий элемент и бросает StopIteration, когда элементы кончились. Цикл for — синтаксический сахар над этим.
Что делает yield?
Превращает функцию в генератор: при вызове тело не выполняется, а возвращается объект-генератор. Каждый next() исполняет тело до следующего yield и замораживает состояние.
GIL и параллелизм
Что такое GIL?
Global Interpreter Lock — блокировка, из-за которой в один момент времени байт-код CPython исполняет только один поток. Многопоточность не ускоряет вычисления на нескольких ядрах.
Дальше почти всегда спрашивают, зачем тогда потоки нужны. Ответ: GIL освобождается на операциях ввода-вывода. Пока поток ждёт ответа сети или диска, остальные работают — поэтому для запросов к API потоки помогают, а для перемножения матриц нет.
Для вычислений — multiprocessing или библиотеки, отпускающие GIL в сишном коде (NumPy). Для ввода-вывода — потоки или asyncio. В Python 3.13 появилась экспериментальная сборка без GIL, но в проде это пока редкость — упомянуть стоит, опираться в ответе нет.
Чем asyncio отличается от потоков?
Потоки переключает операционная система в произвольный момент, корутины — сам цикл событий, и только на await. Отсюда практическая разница: в асинхронном коде между двумя await вы можете быть уверены, что вас не прервали, поэтому гонок меньше. Плата — одна блокирующая операция останавливает весь цикл.
Классы
Что такое MRO?
Method Resolution Order — порядок, в котором Python ищет метод при множественном наследовании. Считается алгоритмом C3, посмотреть можно через ClassName.__mro__. Отсюда же требование вызывать super().__init__(), а не Base.__init__(self): второе ломает цепочку при ромбовидном наследовании.
Чем @staticmethod отличается от @classmethod?
@classmethod первым аргументом получает класс и потому корректно работает с наследованием — типичное применение — альтернативные конструкторы. @staticmethod не получает ничего и по сути просто лежит в пространстве имён класса.
class User:
@classmethod
def from_json(cls, raw):
return cls(**json.loads(raw)) # у наследника вернёт наследника
Что такое __slots__?
Объявление фиксированного набора атрибутов. Экземпляр перестаёт иметь __dict__, что экономит память — заметно, когда объектов миллионы. Цена: нельзя добавить атрибут на лету, и есть тонкости с наследованием.
Как работает менеджер контекста?
Объект с __enter__ и __exit__. Второй вызывается всегда — в том числе при исключении, и если он вернёт истинное значение, исключение будет подавлено.
from contextlib import contextmanager
@contextmanager
def transaction(conn):
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
Практические вопросы
Как найти узкое место в медленном коде?
Вопрос про подход, а не про инструмент. Хороший ответ: сначала измерить (cProfile, line_profiler), потом смотреть на алгоритмическую сложность, и только потом на микрооптимизации. Ответ «переписал бы на C» без замеров — плохой знак.
Чем словарь лучше списка для поиска?
Поиск в словаре — в среднем константа, в списке — линейный проход. На тысяче элементов разницы не заметишь, на миллионе она решает всё. Стоит упомянуть, что в худшем случае (коллизии) словарь деградирует до линейного, хотя на практике это редкость.
Как устроен set и когда он нужен?
Хеш-таблица без значений. Проверка вхождения за константу, автоматическая уникальность. Классическое применение на собеседовании — убрать дубликаты или найти пересечение двух коллекций за линейное время вместо квадратичного.
Как отвечать, если вопрос незнаком
Это случается у всех, и реакция важнее знания. Худший вариант — придумывать. Интервьюер почти всегда понимает предмет лучше вас в этом конкретном вопросе, и выдумка слышна.
Рабочий вариант — сказать, что не сталкивались, и рассуждать вслух от того, что знаете.
«С __slots__ в проде не работал. Из названия и из того, что у объектов обычно есть __dict__, предположу, что это способ зафиксировать набор атрибутов и сэкономить память. Так?»
Это сильный ответ: видно, что человек умеет выводить из первых принципов, а не только вспоминать.
Коротко
- За каждым базовым вопросом стоит уточняющий — готовьте не определение, а следствие.
- Изменяемый аргумент по умолчанию,
functools.wraps,nonlocal, MRO — вопросы, где видно опыт. - GIL: не мешает вводу-выводу, мешает вычислениям.
- На вопросы о производительности отвечайте «сначала измерить».
- Незнакомый вопрос — рассуждайте вслух от известного, не выдумывайте.
Шпора — ИИ-помощник, который слышит вопрос интервьюера и за секунду выводит опору для ответа. Работает на вашем компьютере с любой видеосвязью.
Попробовать бесплатно