Введение
Техническое собеседование — это проверка не столько памяти, сколько глубины понимания фундаментальных концепций языка. Интервьюера интересует не заученный ответ, а ваша способность рассуждать и понимать внутренние механизмы Python.
В этом материале разобран пул из 10 вопросов, которые регулярно используются для оценки Junior-разработчиков. Каждый вопрос рассматривается с двух позиций: что представляет собой корректный ответ и какую именно компетенцию кандидата он проверяет.
Цель статьи — систематизировать знания по основным темам и подготовить вас к демонстрации не только синтаксиса, но и логики работы языка.
Вопрос 1: В чем ключевое различие между list и tuple?
Краткий ответ:
Ключевое различие заключается в изменяемости (mutability). list — изменяемый тип данных, его содержимое можно изменить после создания. tuple — неизменяемый (immutable), его изменить нельзя.
Развернутый ответ:
Из-за неизменяемости у tuple есть важные следствия и отличия от list:
Хешируемость (Hashability):
tupleявляется хешируемым объектом, поэтому его можно использовать в качестве ключа в словаре (dict) или как элемент множества (set).listне является хешируемым.Производительность: Как правило,
tupleзанимает меньше памяти и создается быстрее, чемlistс теми же элементами.Семантика и назначение: По принятому соглашению,
listиспользуется для хранения гомогенных (однородных) последовательностей данных, аtuple— для гетерогенных (разнородных), фактически играя роль структуры или записи (например,(ip_address, port)).
Что проверяет этот вопрос:
Понимание фундаментальной концепции изменяемых и неизменяемых типов в Python. Это базовое знание, которое влияет на безопасность данных, производительность и архитектуру кода.
Вопрос 2: Опишите разницу между list, set и dict. В каких случаях вы будете использовать каждый из них?
Краткий ответ:
list— упорядоченная, индексируемая коллекция, допускающая дубликаты.set— неупорядоченная коллекция, содержащая только уникальные элементы.dict— неупорядоченная (в Python < 3.7) коллекция пар «ключ-значение» с уникальными ключами.
Развернутый ответ (сценарии использования):
-
list(список): Используется, когда важен порядок элементов или когда допустимы дубликаты.Пример: Последовательность шагов в алгоритме, история транзакций, список комментариев к посту.
-
set(множество): Используется, когда нужна уникальность элементов и порядок не важен. Также он оптимизирован для операций проверки на вхождение и математических операций с множествами (объединение, пересечение).Пример: Хранение уникальных ID пользователей, определение общих интересов у двух пользователей, удаление дубликатов из списка.
-
dict(словарь): Используется для хранения связанных данных в виде пар «ключ-значение» для быстрого доступа к значению по ключу.Пример: Представление объекта (e.g.,
{'id': 1, 'name': 'John Doe'}), кэширование результатов вычислений, сопоставление данных из разных источников.
Что проверяет этот вопрос:
Способность кандидата выбрать адекватную и эффективную структуру данных для решения конкретной задачи. Неправильный выбор может привести к усложнению кода и снижению производительности.
Вопрос 3: Проанализируйте следующий код. Что он выведет и почему?
def add_item(item, some_list=[]):
some_list.append(item)
return some_list
print(add_item(1))
print(add_item(2))
Краткий ответ:
Код выведет [1] и затем [1, 2]. Проблема заключается в том, что изменяемый объект (list) используется в качестве значения по умолчанию.
Развернутый ответ:
Аргументы по умолчанию в Python вычисляются и создаются один раз — в момент определения (интерпретации) функции, а не при каждом её вызове.
Когда
def add_item(...)исполняется, в памяти создается один объект-список[], и ссылка на него сохраняется как значение по умолчанию для параметраsome_list.Первый вызов
add_item(1)использует этот единственный экземпляр списка, добавляет в него1и возвращает[1].Второй вызов
add_item(2)использует тот же самый экземпляр списка, который уже содержит1, добавляет в него2и возвращает[1, 2].
Идиоматически правильное решение — использовать None в качестве "сигнального" значения по умолчанию:
def add_item(item, some_list=None):
if some_list is None:
some_list = []
some_list.append(item)
return some_list
Что проверяет этот вопрос:
Это один из важнейших "проверочных" вопросов. Он тестирует глубокое понимание жизненного цикла объектов в Python и разницы между временем определения функции и временем её вызова. Непонимание этого механизма — частый источник трудноуловимых багов.
Вопрос 4: Что такое *args и **kwargs и для чего они используются?
Краткий ответ:
Это специальные синтаксические конструкции для передачи в функцию произвольного числа аргументов. *args собирает все неименованные (позиционные) аргументы в кортеж (tuple). **kwargs собирает все именованные (ключевые) аргументы в словарь (dict).
Развернутый ответ:
Порядок их использования в сигнатуре функции строго определен: сначала идут обычные аргументы, затем *args, затем **kwargs.
*args(arguments): Позволяет передать переменное количество позиционных аргументов.**kwargs(keyword arguments): Позволяет передать переменное количество именованных аргументов.
Пример:
def process_data(required_arg, *args, **kwargs):
print(f"Обязательный аргумент: {required_arg}")
print(f"Позиционные аргументы (*args): {args}")
print(f"Именованные аргументы (**kwargs): {kwargs}")
process_data(
"START",
10, 20, "go",
status="active",
user_id=123
)
Вывод:
Обязательный аргумент: START
Позиционные аргументы (*args): (10, 20, 'go')
Именованные аргументы (**kwargs): {'status': 'active', 'user_id': 123}
Что проверяет этот вопрос:
Знание синтаксиса определения функций и понимание, как создавать гибкие и расширяемые API. Это особенно важно при работе с декораторами, наследованием и передачей аргументов между функциями.
Вопрос 5: Что такое comprehensions? Приведите пример для списка.
Краткий ответ:
Comprehensions (включения) — это лаконичный и читаемый синтаксис для создания коллекций (list, dict, set) на основе итерируемых объектов. Они позволяют заменить громоздкие циклы for одной выразительной строкой кода.
Развернутый ответ:
Наиболее распространенный пример — list comprehension (списковое включение). Оно объединяет цикл for, опциональное условие if и выражение для создания нового элемента в одну конструкцию.
Сравнение:
-
Традиционный подход с циклом
for:squares_of_evens = [] for i in range(10): if i % 2 == 0: squares_of_evens.append(i * i) -
Идиоматичный подход с list comprehension:
squares_of_evens = [i * i for i in range(10) if i % 2 == 0]
Оба фрагмента кода дают одинаковый результат ([0, 4, 16, 36, 64]), но второй вариант считается более "питоничным", так как он декларативен (описывает что нужно получить, а не как) и часто работает быстрее.
Что проверяет этот вопрос:
Знакомство кандидата с идиомами языка Python. Умение использовать comprehensions — это маркер того, что разработчик пишет не просто работающий, а чистый, лаконичный и эффективный код.
Вопрос 6: Чем f-строки отличаются от метода .format() и почему они предпочтительнее?
Краткий ответ:
F-строки (форматированные строковые литералы) — это современный, более быстрый и удобный способ форматирования строк, представленный в Python 3.6.
Развернутый ответ:
Основные преимущества f-строк перед .format():
-
Синтаксис и читаемость: f-строки позволяют встраивать переменные и выражения напрямую в строковый литерал, что делает код менее многословным и более наглядным.
.format():"User {name} has ID {user_id}." .format(name=user_name, user_id=uid)f-string:
f"User {user_name} has ID {uid}."
Производительность: F-строки, как правило, работают быстрее, поскольку они парсятся и вычисляются на этапе выполнения, а не через вызов метода.
-
Возможность встраивания выражений: Внутри фигурных скобок f-строки можно размещать любые валидные Python-выражения, что невозможно в
.format()без предварительного вычисления.Пример:
f"The final price is {price * (1 + tax_rate):.2f}."
Что проверяет этот вопрос:
Знание современных возможностей языка. Использование f-строк вместо устаревших методов (.format() или %) показывает, что кандидат следит за развитием Python и применяет актуальные лучшие практики.
Вопрос 7: Что такое __init__ и self в контексте классов Python?
Краткий ответ:
__init__ — это специальный метод-конструктор, который вызывается для инициализации нового экземпляра класса. self — это общепринятое имя для первого параметра метода, который представляет собой ссылку на сам экземпляр класса.
Развернутый ответ:
__init__(self, ...): Это один из "магических" методов Python. Он автоматически вызывается сразу после создания объекта в памяти. Его основная задача — установить начальное состояние объекта, то есть присвоить значения его атрибутам (полям).self: Это не ключевое слово, а соглашение об именовании. Черезselfметоды экземпляра получают доступ к его атрибутам и другим методам. Когда вы вызываетеmy_object.my_method(arg1), Python автоматически преобразует этот вызов вMyClass.my_method(my_object, arg1), передавая сам объект первым аргументом.
Пример:
class Dog:
def __init__(self, name):
# self.name создает атрибут 'name' для этого конкретного экземпляра
self.name = name
def bark(self):
# self.name используется для доступа к атрибуту экземпляра
print(f"{self.name} says woof!")
my_dog = Dog("Rex") # Вызов __init__ с name="Rex"
my_dog.bark() # Python передает my_dog в качестве 'self'
Что проверяет этот вопрос:
Понимание самых основ объектно-ориентированного программирования (ООП) в Python. Без четкого осознания роли конструктора и ссылки на экземпляр невозможно корректно создавать и использовать классы.
Вопрос 8: Что такое генератор? В чем его ключевое преимущество перед функцией, возвращающей список?
Краткий ответ:
Генератор — это итератор, который порождает значения "на лету" с помощью ключевого слова yield, не сохраняя их все в памяти. Его главное преимущество — эффективность по памяти.
Развернутый ответ:
Когда обычная функция с return возвращает список, она сначала должна полностью сформировать этот список в памяти. Если данных много, это может привести к высокому потреблению RAM или даже к MemoryError.
Функция-генератор использует yield. Когда yield вызывается, функция "приостанавливает" свое выполнение и отдает значение. При следующем запросе значения выполнение возобновляется с того же места. Таким образом, в памяти одновременно находится только одно значение.
Сценарий использования:
Обработка больших файлов, работа с бесконечными последовательностями, сложные конвейеры обработки данных.
Пример:
# Функция, возвращающая список (неэффективно для больших n)
def list_first_n_squares(n):
result = []
for i in range(n):
result.append(i*i)
return result
# Функция-генератор (эффективно по памяти)
def gen_first_n_squares(n):
for i in range(n):
yield i*i
# Использование:
# sum(list_first_n_squares(100_000_000)) # Потенциально вызовет MemoryError
# sum(gen_first_n_squares(100_000_000)) # Работает без проблем
Что проверяет этот вопрос:
Понимание концепции "ленивых вычислений" и итераторов. Умение использовать генераторы — признак разработчика, который задумывается об эффективности и масштабируемости своего кода.
Вопрос 9: Что такое декоратор в Python?
Краткий ответ:
Декоратор — это функция, которая принимает другую функцию в качестве аргумента, расширяет её поведение, не изменяя её исходный код, и возвращает новую, "обернутую" функцию.
Развернутый ответ:
Декораторы являются реализацией концепции функций высшего порядка и активно используют замыкания. Синтаксис с символом @ (@my_decorator) — это "синтаксический сахар" для более явного вызова my_function = my_decorator(my_function).
Типичные сценарии использования:
Логирование вызовов функций и времени их выполнения.
Кэширование результатов (мемоизация).
Проверка прав доступа или аутентификации.
Валидация аргументов функции.
Пример (декоратор для замера времени выполнения):
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Функция {func.__name__} выполнялась {end_time - start_time:.4f} секунд")
return result
return wrapper
@timer_decorator
def heavy_calculation(n):
"""Что-то долго вычисляет."""
sum(i*i for i in range(n))
heavy_calculation(10_000_000)
Что проверяет этот вопрос:
Понимание более сложных концепций языка: функции как объекты первого класса, замыкания, функции высшего порядка. Умение читать и понимать декораторы — необходимый навык для работы с большинством современных Python-фреймворков (Flask, Django, FastAPI и др.).
Вопрос 10: Что такое GIL и как он влияет на многопоточность в Python?
Краткий ответ:
GIL (Global Interpreter Lock) — это глобальный замок интерпретатора, мьютекс, который в реализации CPython позволяет только одному потоку исполнять Python-байт-код в любой момент времени внутри одного процесса.
Развернутый ответ:
Основная задача GIL — упростить управление памятью в CPython, защищая доступ к Python-объектам и предотвращая состояние гонки на уровне C-реализации. Однако у этого есть важное следствие для параллельных вычислений:
Для CPU-bound задач (интенсивные вычисления): Модуль
threadingне обеспечивает реального параллелизма на многоядерных процессорах. Из-за GIL потоки, выполняющие вычисления, будут исполняться поочередно, а не одновременно, и производительность не увеличится, а может даже незначительно упасть из-за накладных расходов на переключение контекста.Для I/O-bound задач (ожидание сети, диска):
threadingостается эффективным инструментом. Когда поток выполняет блокирующую операцию ввода-вывода (например, делает сетевой запрос), он освобождает GIL, позволяя другому потоку выполняться в это время. Это создает псевдо-параллелизм, который значительно повышает производительность приложений, работающих с сетью или файловой системой.
Для достижения истинного параллелизма при решении CPU-bound задач в Python следует использовать модуль multiprocessing, который обходит GIL путем создания отдельных процессов, каждый со своим собственным интерпретатором Python и пространством памяти.
Что проверяет этот вопрос:
Это продвинутый вопрос для Junior-позиции, и знание о GIL — серьезный плюс. Он проверяет, понимает ли кандидат ограничения стандартной модели многопоточности в Python и осознает ли разницу между конкурентностью и параллелизмом. Ответ на этот вопрос показывает, что разработчик задумывается о производительности и может выбрать правильный инструмент (threading или multiprocessing) в зависимости от типа задачи.
Заключение
Помните, что техническое собеседование — это диалог, а не экзамен. Важнее не зазубрить ответы, а продемонстрировать ход мысли и понимание базовых принципов, стоящих за кодом. Умение рассуждать и объяснять свой выбор ценится гораздо выше, чем мгновенный, но поверхностный ответ.
Безусловно, некоторые из представленных ответов могут быть спорными, а подходы к решению — различаться в зависимости от проекта или команды. Если вы с чем-то не согласны, заметили неточность или хотите предложить свой, более элегантный вариант ответа, я всегда открыт к обсуждению. Для таких технических дискуссий у меня есть группа в Telegram
Удачи на собеседованиях!
Комментарии (6)

KonstantinTokar
23.10.2025 17:453,5,7,8,9,10
Некоторые пункты спорные, но если бы их не было было бы лучше.

nagadit
23.10.2025 17:45Хешируемость (Hashability):
tupleявляется хешируемым объектом, поэтому его можно использовать в качестве ключа в словаре (dict) или как элемент множества (set).listне является хешируемым.
Доказывать на собеседовании, что tuple хешируемый - главная ошибка.
Просто запусти вот это и все станет понятно:
a = tuple([[1,2,3],1]) d = {a:5}Хешируемость и изменяемость — это совершенно разные понятия никак не связанные друг с другом.
У tuple (как и у большинства контейнеров в python), хеш берется не от самого контейнера, а от элементов внутри него.
Хешируемость определяется банальными магическими методами eq и hash. Ты можешь даже list сделать хешируемым))
Изменяемость же определятся логикой работы этих объектов в CPython.
p.s. Доп вопрос на собес: если ты создашь свой класс, например:
class A: passА потом создашь его экземпляр и добавишь как ключ в словарь, то все сработает:
a = A() d = {a:1}Но сразу после этого вы можете добавить полей в этот класс:
a.lol = 10и при этом его хеш не сломается:
print(d[a]) -> 1Как же так? - велком в коменты
KonstantinTokar
Этот же набор вопросов можно (частично) использовать для обоснования того, что питон был плохо спроектирован.
GBR-613
С Javascript то же самое.
avkritsky
А какие именно вопросы? Я, максимум, мог бы только про третий вопрос так сказать.
KonstantinTokar
ответил ниже, промахнулся