На собеседовании Middle Fullstack-разработчика проверяли прежде всего фундамент Python: конкурентность, модель объектов, коллекции и протоколы языка. Знания Django и React оставили для следующей части. Здесь важен не список терминов, а способность связать механизм с практической задачей.
Что такое GIL и когда нужны asyncio, потоки или процессы
В обычной сборке CPython GIL позволяет только одному потоку одновременно выполнять байткод Python. При этом потоки остаются полезными: во время ожидания сети, файла или ответа базы интерпретатор может переключиться на другую работу. Современный Python также поддерживает экспериментальные free-threaded сборки без GIL, поэтому формулировка «Python всегда однопоточный» уже неверна. Детали собраны в глоссарии Python.
Выбор начинается с характера нагрузки:
| Задача | Подход | Почему |
|---|---|---|
| Много сетевых ожиданий | asyncio |
Корутины переключаются в точках await |
| Блокирующая I/O-библиотека | Потоки | Можно выполнять несколько ожиданий параллельно |
| Тяжёлые вычисления на Python | Процессы | Отдельные интерпретаторы используют несколько ядер |
asyncio не обязано быть быстрее потоков в любой задаче. Оно выигрывает там, где зависимостей много, они неблокирующие и команда контролирует точки переключения.
Как устроен dict и каким должен быть его ключ
dict сопоставляет хэшируемые ключи значениям. Для пользовательского класса особенно важно правило: равные объекты обязаны иметь одинаковый хэш, а хэш ключа не должен меняться, пока ключ находится в словаре. Именно поэтому изменяемые list, dict и set нельзя использовать как ключи.
Кортеж подходит не автоматически. (1, "python") хэшируем, а (1, []) — нет, потому что список внутри изменяем. В своём классе обычно согласуют __eq__() и __hash__(); подробные требования есть в модели данных Python.
Типичная ошибка. Говорить, что любой неизменяемый объект всегда годится для ключа.
Как лучше. Проверить хэшируемость объекта и стабильность полей, участвующих в сравнении и хэше.
Как Python передаёт аргументы в функцию
Python присваивает локальному имени ссылку на уже существующий объект. Поэтому функция может изменить переданный список на месте, но простое присваивание нового списка локальному параметру не заменит объект у вызывающего кода.
def add_item(items: list[str]) -> None:
# append меняет тот же список, который передал вызывающий код.
items.append("Django")
def replace_items(items: list[str]) -> None:
# Здесь локальное имя получает новый объект, исходный список не меняется.
items = ["React"]
stack = ["Python"]
add_item(stack)
replace_items(stack)
print(stack) # ['Python', 'Django']Этот пример точнее формулы «изменяемое передаётся по ссылке, неизменяемое — по значению»: механизм передачи один, различается поведение самого объекта.
Почему изменяемый параметр по умолчанию опасен
Значение параметра по умолчанию вычисляется один раз при создании функции. Если поставить items=[], один список будет накапливать данные между вызовами. FAQ Python рекомендует использовать None и создавать объект внутри функции.
def collect(value: int, items: list[int] | None = None) -> list[int]:
# Для каждого вызова без items создаём отдельный список.
if items is None:
items = []
items.append(value)
return items
print(collect(1)) # [1]
print(collect(2)) # [2], а не [1, 2]Чем итератор отличается от генератора
Итератор — объект с __next__(), который выдаёт следующий элемент и завершает работу исключением StopIteration. Генератор — удобный способ создать итератор функцией с yield: состояние функции сохраняется между обращениями. Официальное описание протокола есть в разделе Iterator Types.
Генераторы полезны для больших файлов и потоков данных, потому что не обязаны хранить весь результат в памяти. Но они одноразовые: после полного обхода тот же объект заново данные не выдаст.
Чем отличаются обычный, классовый и статический методы
Обычный метод получает self и работает с конкретным экземпляром. @classmethod получает cls; его часто используют для альтернативных конструкторов и операций над состоянием класса. @staticmethod не получает ни экземпляр, ни класс и подходит для функции, логически связанной с классом.
Контекстный менеджер решает другую задачу: методы __enter__() и __exit__() управляют входом и выходом из блока with. Они помогают гарантированно освободить файл, соединение или блокировку даже при исключении.
Итог: получил бы кандидат оффер
Фактический результат встречи не опубликован. По доступной части кандидат скорее прошёл бы дальше, но уверенного Middle-оффера мы бы пока не прогнозировали. Он знал основные идеи и спокойно рассуждал, однако путался в передаче аргументов, генераторах и видах методов. Для Middle это поправимые, но заметные пробелы: после уточнения интервьюер ждёт точной модели, а не приблизительного определения.
Для подготовки полезно проверить себя:
- Объясняю выбор между asyncio, потоками и процессами на конкретной нагрузке.
- Могу написать хэшируемый класс и объяснить связь
__eq__с__hash__. - Показываю разницу между мутацией объекта и перепривязкой имени.
- Пишу безопасный параметр по умолчанию без подсказки.
- Различаю итератор, генератор и итерируемый объект.
Источники
частые вопросы
Короткие ответы
Нужно ли Middle Python-разработчику знать GIL?
Да. Важно не только дать определение, но и объяснить выбор между asyncio, потоками и процессами для I/O- и CPU-нагрузки.
Можно ли использовать кортеж как ключ словаря?
Можно, если все элементы кортежа хэшируемы. Кортеж со списком внутри ключом быть не сможет.
Как тренировать такие вопросы перед собеседованием?
Отвечайте вслух по схеме «определение — механизм — ограничение — пример», затем проверяйте спорные детали в документации и кодом.


