Вопросы на собеседовании: Python-разработчик
100 реальных вопросов с образцовыми ответами и пояснениями для уровня Junior.
Смотреть пример резюме: Python-разработчик →Тренировка флешкарточками
Интервальное повторение · Hunter Pass
Вопросы
Списки, кортежи и множества решают разные задачи в зависимости от порядка, изменяемости и уникальности элементов.
- Список представляет собой упорядоченную изменяемую последовательность.
- Кортеж упорядочен и неизменяем, поэтому может быть ключом словаря, если его содержимое хешируемо, в отличие от списка.
- Множество хранит уникальные хешируемые элементы без определённого порядка, автоматически убирает дубликаты и в среднем проверяет принадлежность за O(1).
Зачем это спрашивают: Интервьюер ждёт свойства плюс практические следствия вроде ключей словаря и скорости проверки принадлежности, а не только определения.
Изменяемость определяет, может ли объект меняться после создания, и влияет на способы его использования.
- Списки, словари и множества изменяемы, а числа int и float, строки, кортежи и frozenset неизменяемы.
- Две переменные могут ссылаться на один изменяемый список, поэтому изменение через одно имя будет видно через другое.
- Неизменяемость делает значение хешируемым и позволяет использовать его как ключ словаря или элемент множества.
Зачем это спрашивают: Понимание общих ссылок и побочных эффектов мутации - корень многих джуновских багов, поэтому интервьюеры проверяют его рано.
Словарь Python представляет собой хеш-таблицу с быстрым доступом по ключу.
- Он использует хеш ключа для поиска значения, поэтому поиск, вставка и удаление в среднем работают за O(1).
- Ключ должен быть хешируемым, например строкой, числом или кортежем неизменяемых значений; список или словарь ключом быть не может.
- Начиная с Python 3.7 словари сохраняют порядок вставки, но это не заменяет явную сортировку.
Зачем это спрашивают: Требование хешируемости ключей и интуиция про O(1) показывают, понимает ли кандидат структуру глубже синтаксиса.
Срез извлекает часть последовательности с помощью записи sequence[start:stop:step].
- Индекс start включается, stop не включается, а любую часть записи можно опустить.
- Отрицательные индексы отсчитываются с конца, поэтому items[-3:] возвращает последние три элемента, а items[::-1] переворачивает последовательность.
- Срез списка создаёт новый список, поэтому items[:] можно использовать как быструю поверхностную копию.
Зачем это спрашивают: Свободное обращение с исключаемой правой границей и отрицательными индексами - быстрый признак повседневной практики в Python.
Списковое включение лучше всего подходит для простого и понятного преобразования или фильтрации с созданием списка.
- Выражение [x * 2 for x in nums if x > 0] преобразует и фильтрует значения в одной строке.
- Такой вариант считается питоничным, пока логика преобразования и фильтрации легко читается.
- Обычный цикл понятнее, если нужны несколько инструкций, побочные эффекты или глубокая вложенность.
Зачем это спрашивают: Интервьюер проверяет и идиоматичный стиль, и здравый смысл отказаться от него, когда страдает читаемость.
В Python есть словарные и множественные включения, а также ленивые генераторные выражения.
- Словарное включение может выглядеть как {u.id: u.name for u in users}, а множественное как {x % 10 for x in nums}.
- Генераторное выражение, например sum(x * x for x in nums), выдаёт значения лениво и не создаёт промежуточный список.
- Выбор в основном зависит от того, нужен ли весь результат в памяти одновременно.
Зачем это спрашивают: Знание генераторных выражений и причин их экономности отделяет практиков от читателей туториалов.
Синтаксис *args и **kwargs позволяет функциям принимать или передавать дальше гибкий набор аргументов.
- *args собирает дополнительные позиционные аргументы в кортеж.
- **kwargs собирает дополнительные именованные аргументы в словарь.
- При вызове func(*items, **options) распаковывает последовательность и словарь, что удобно в обёртках и декораторах, передающих аргументы без изменений.
Зачем это спрашивают: Сценарий проброса аргументов показывает, что кандидат видел это в реальном коде, а не только в таблицах синтаксиса.
Изменяемый аргумент по умолчанию используется совместно разными вызовами и может сохранять неожиданное состояние.
- В def add_item(item, items=[]) список создаётся один раз при определении функции, а не при каждом вызове.
- Вызовы без items изменяют один и тот же список, поэтому новый вызов может получить значения, оставшиеся после предыдущего.
- Следует задать items=None по умолчанию и создать items = [] внутри функции, если items is None.
Зачем это спрашивают: Эта классическая ловушка проверяет, знает ли кандидат, что значения по умолчанию вычисляются один раз при определении.
Python ищет имена по порядку в локальной, объемлющей, глобальной и встроенной областях видимости, то есть по правилу LEGB.
- Читать переменную из внешней области можно без дополнительного объявления.
- Присваивание имени внутри функции создаёт локальную переменную, если не указать global или nonlocal.
- Поэтому изменение глобального списка может работать, а переприсваивание глобального счётчика без global вызывает UnboundLocalError.
Зачем это спрашивают: Правило присваивание-создаёт-локальную и пример с UnboundLocalError выдают настоящее понимание.
Python использует передачу через присваивание, то есть функция получает ссылку на тот же объект.
- Эта модель не является передачей по значению или по ссылке в классическом смысле.
- Изменение переданного списка внутри функции меняет список вызывающей стороны, потому что обе ссылки ведут на один объект.
- Перепривязка параметра к новому объекту меняет только локальное имя и не затрагивает вызывающую сторону.
Зачем это спрашивают: Проверяется модель ссылок на объекты, фундамент для рассуждений о побочных эффектах.
Операторы == и is сравнивают соответственно равенство значений и идентичность объектов.
- Оператор == вызывает __eq__ для сравнения значений.
- Оператор is проверяет, указывают ли два имени на один объект, поэтому два равных списка могут пройти проверку ==, но не is.
- Сравнение по идентичности уместно для синглтонов, особенно в записи x is None, потому что объект None существует в одном экземпляре.
Зачем это спрашивают: Конвенция x is None - та практическая деталь, которую интервьюеры слушают сверх определения.
Поверхностная и глубокая копии различаются тем, остаются ли вложенные объекты общими с оригиналом.
- Поверхностная копия создаёт новый внешний контейнер, но сохраняет ссылки на прежние внутренние объекты.
- Поэтому list(data) и data[:] по-прежнему разделяют вложенные списки, а изменение вложенного словаря может затронуть оригинал.
- copy.deepcopy рекурсивно копирует содержимое и создаёт полностью независимую структуру.
Зачем это спрашивают: Баг с вложенной структурой показывает, что кандидата это реально кусало.
F-строки обычно предпочитают для форматирования, потому что они прямолинейны, читаемы и быстры.
- Они встраивают выражения напрямую, как в f'{user.name} has {count} items', и поддерживают спецификаторы вроде f'{price:.2f}'.
- Начиная с Python 3.8 запись f'{value=}' выводит выражение вместе с его значением для быстрой отладки.
- Процентное форматирование и str.format всё ещё встречаются в старом коде, а в логировании ленивое процентное форматирование используется намеренно.
Зачем это спрашивают: Спецификаторы формата и отладочная форма со знаком = показывают беглость сверх базового синтаксиса.
Многократная склейка строк через + в большом цикле неэффективна, потому что строки неизменяемы.
- Каждый += создаёт новую строку и копирует всё предыдущее содержимое, из-за чего на больших данных цикл может стать квадратичным.
- Идиоматичная альтернатива состоит в том, чтобы собрать части в список и один раз вызвать ''.join(parts).
- Для нескольких склеек разница несущественна, поэтому проблема важна прежде всего в циклах по множеству элементов.
Зачем это спрашивают: Проверяется, связывает ли кандидат неизменяемость с реальным последствием для производительности.
Блоки try, except, else и finally разделяют защищаемый код, обработку ошибок, успешное продолжение и очистку ресурсов.
- Сначала выполняется код в try, а подходящий блок except обрабатывает возникшее исключение.
- Блок else запускается только при завершении try без исключения и отделяет успешный путь от защищаемого кода.
- Блок finally выполняется всегда и подходит для очистки, например закрытия ресурсов, которыми не управляет with.
Зачем это спрашивают: Знание, зачем нужен else, отличает тех, кто читал дальше минимального шаблона try/except.
Голый except опасен, потому что может скрыть настоящие ошибки и исключения, управляющие завершением программы.
- Он ловит KeyboardInterrupt, SystemExit и ошибки вроде NameError, после чего программа может продолжить работу в некорректном состоянии.
- Следует ловить самое конкретное исключение, которое код способен обработать, например FileNotFoundError или ValueError.
- Если на верхнем уровне нужен широкий обработчик, стоит ловить Exception, записывать полный трейсбек и затем пробрасывать ошибку дальше или явно завершать работу.
Зачем это спрашивают: Интервьюер проверяет инстинкт держать отказы громкими и конкретными, а не глушить их.
Исключения должны сообщать о недопустимом вводе или невосстановимом состоянии как можно ближе к причине.
- Функция может явно отклонить входные данные, например через raise ValueError('amount must be positive').
- Собственный подкласс Exception, такой как PaymentDeclinedError, позволяет вызывающему коду ловить конкретные отказы, не перехватывая посторонние ValueError.
- Раннее исключение с понятным сообщением безопаснее возврата None, после которого сбой произойдёт далеко от причины.
Зачем это спрашивают: Раннее поднятие конкретных типов показывает, что кандидат проектирует пути ошибок, а не латает их постфактум.
Протокол итератора даёт Python единый способ обходить списки, файлы, генераторы, словари и другие итерируемые объекты.
- Итерируемый объект возвращает итератор при вызове iter().
- Итератор реализует __next__ и выдаёт значения, пока не поднимет StopIteration.
- Цикл for один раз вызывает iter(), затем многократно вызывает next() и останавливается при StopIteration.
Зачем это спрашивают: Объяснение, во что разворачивается цикл for, доказывает, что концепция понята, а не заучена.
Генератор выдаёт значения по одному, поэтому подходит для больших или бесконечных последовательностей.
- Генераторная функция использует yield и приостанавливает выполнение между выдаваемыми значениями.
- Построчная обработка большого лог-файла требует постоянного объёма памяти, тогда как возврат списка загрузил бы весь результат.
- Генератор можно пройти только один раз, и у него нет len().
Зачем это спрашивают: Аргумент про память плюс ограничение одного прохода - полная картина уровня джуна, которую ждут интервьюеры.
range() возвращает ленивый объект последовательности, а не заранее созданный список.
- Он вычисляет значения по требованию, поэтому даже range(10**9) почти не занимает памяти.
- Объект range поддерживает len(), индексацию и быструю проверку принадлежности целых чисел.
- list(range(n)) нужен только тогда, когда действительно требуется список; в циклах лучше использовать ленивый range напрямую.
Зачем это спрашивают: Маленький вопрос, быстро показывающий, думает ли кандидат о ленивости и памяти.
Закрытые вопросы
- 21
Что делают enumerate() и zip() и почему они предпочтительнее арифметики индексов?
indexes - 22
Какие значения в Python ложные и как это влияет на условия if?
python - 23
Что такое None и как правильно его проверять?
- 24
Как читать и записывать текстовые файлы в Python?
python - 25
Что такое контекстный менеджер и какую проблему решает оператор with?
context-managers - 26
Чем sorted() отличается от list.sort() и как работает параметр key?
sorting - 27
Что такое lambda и когда она уместна?
lambda - 28
Вы используете map() и filter() или comprehension?
comprehensions - 29
Что умеет распаковка в Python?
python - 30
Как Python управляет памятью и сборкой мусора?
gcmemorypython - 31
Что такое декораторы и как их использовать?
decorators - 32
Как безопасно прочитать ключ, которого может не быть в словаре?
- 33
Чем отличаются list.remove(), list.pop() и del при удалении элементов?
- 34
Что происходит при создании экземпляра класса и что такое self?
- 35
В чём разница между атрибутами экземпляра и атрибутами класса?
- 36
Как работает наследование в Python и что делает super()?
ooppythonownership - 37
В чём разница между __str__ и __repr__?
dunder - 38
Что нужно реализовать, чтобы два объекта вашего класса сравнивались как равные по значению?
- 39
Что делает декоратор @property?
decoratorsproperties - 40
В чём разница между @staticmethod и @classmethod?
oop - 41
Когда вы создаёте класс вместо обычных функций?
- 42
Что такое модули и пакеты и что происходит при импорте?
- 43
Что делает if __name__ == '__main__' и зачем это нужно?
modules - 44
Как вы управляете зависимостями проекта через pip?
dependenciespip - 45
Что такое виртуальное окружение и почему оно нужно каждому проекту?
venv - 46
Какие инструменты из модуля collections вы находите самыми полезными?
collections - 47
Как вы работаете с датами и временем в Python?
python - 48
Как переносимо работать с путями к файлам?
- 49
Как сериализовать и парсить JSON в Python?
serializationpython - 50
Что такое подсказки типов и заставляет ли Python их соблюдать?
pythontyping - 51
Зачем писать автотесты и что бы вы протестировали первым в небольшом проекте?
testing - 52
Как pytest находит и запускает тесты?
pytest - 53
Как вы структурируете отдельный тест, чтобы он оставался читаемым?
- 54
Для чего нужны фикстуры pytest?
pytestfixtures - 55
Как в pytest прогнать один тест на множестве входов?
pytest - 56
Когда и зачем вы мокаете в тестах?
mocking - 57
Чем pytest отличается от стандартного модуля unittest?
pytestunittest - 58
Как вы отлаживаете сбоящий Python-скрипт, кроме добавления принтов?
python - 59
Как читать трейсбек Python?
python - 60
В чём разница между TypeError и ValueError и когда появляются KeyError и IndexError?
indexes - 61
Вы застряли на баге на два часа. Каков ваш процесс?
problem-solvingconcurrency - 62
Зачем использовать модуль logging вместо print в настоящем коде?
logging - 63
Опишите ваш базовый рабочий процесс в git при работе над задачей.
git - 64
Почему команды работают через ветки и пул-реквесты, а не коммитят прямо в главную ветку?
code-review - 65
Чем git fetch отличается от git pull и что делать с конфликтами слияния?
git - 66
Что должно лежать в .gitignore Python-проекта?
gitpython - 67
Как выполнить запрос к базе данных из Python?
databasequeriespython - 68
Что такое SQL-инъекция и как защититься от неё в Python?
sqlinjectionpython - 69
Что такое ORM и каковы его плюсы и минусы на вашем уровне?
orm - 70
Задача требует получить число заказов на пользователя из SQL-базы. Как вы к этому подойдёте?
sqldatabase - 71
Что такое транзакция базы данных и когда она нужна в коде приложения?
databasetransactions - 72
С какими основными HTTP-методами и классами статус-кодов вы имеете дело?
httpstatus-codes - 73
Как вызвать HTTP-API из Python библиотекой requests?
httppython - 74
API возвращает вложенный JSON-ответ. Как безопасно извлечь из него данные?
api - 75
Как вы обращаетесь с конфигурацией и секретами вроде API-ключей в Python-проекте?
soft-skillsapisecrets - 76
Как обработать CSV-файл в Python?
concurrencypython - 77
Как бы вы посчитали самые частые слова в текстовом файле?
- 78
Как обработать файл, который больше вашей оперативной памяти?
concurrency - 79
Скрипт падает с UnicodeDecodeError при чтении файла. Что происходит и что делать?
- 80
Как добавить скрипту аргументы командной строки?
python - 81
Как должен вести себя консольный скрипт, когда что-то идёт не так?
- 82
Внешний API, который зовёт ваш скрипт, отказывает время от времени. Как сделать скрипт устойчивым?
api - 83
Что значит, что Python - интерпретируемый язык?
python - 84
Что такое GIL простыми словами и что он значит для вашего кода?
gil - 85
Что такое PEP 8 и насколько строго вы ему следуете?
style - 86
На что вы обращаете внимание, когда ревьюят ваш код и когда вы читаете чужой?
- 87
Что такое маршрут или эндпоинт в веб-фреймворках вроде Flask или FastAPI?
frameworksendpoints - 88
Как бы вы выбирали между Django, Flask и FastAPI для нового проекта?
frameworks - 89
pip install прошёл успешно, но скрипт падает с ModuleNotFoundError. Что проверяете?
pippackaging - 90
Как вы структурируете небольшой Python-проект?
python - 91
Как правильно документировать функцию?
- 92
Какие сигналы говорят, что функцию пора рефакторить, и как делать это безопасно?
refactoring - 93
Ваш скрипт работает слишком медленно. Что вы делаете в первую очередь?
- 94
Нужно вытащить таймстемп и сообщение об ошибке из каждой строки лог-файла. Как подойдёте?
- 95
Вам прилетает тикет с текстом: экспорт сломан, почини. Что вы делаете?
- 96
Как вы балансируете между разобраться самому и попросить помощи?
- 97
Ваше влитое изменение что-то сломало в продакшене. Что вы делаете?
- 98
Как вы продолжаете расти как junior Python-разработчик?
python - 99
Сеньор на ревью просит изменения, с которыми вы не согласны. Как вы поступаете?
soft-skillsconflict - 100
Задача занимает намного больше времени, чем вы оценили. Когда и как вы об этом сообщаете?
communicationestimation