Python на собеседовании — это не вопросы про синтаксис из учебника, а практические задачи: разобраться в чужом коде, найти баг, написать функцию с граничными случаями, объяснить, как работает изменяемый объект по умолчанию. Проверяют не то, помните ли вы ключевые слова языка, а понимаете ли вы, что происходит «под капотом» у привычных конструкций.
Python спрашивают у тестировщиков (для автоматизации и работы со скриптами), аналитиков, backend и data-инженеров — почти на любом техническом интервью, где в вакансии упомянут этот язык. Набор тем при этом ограничен: из собеса в собес повторяются одни и те же ловушки и вопросы на понимание, и если разобраться в их механике, закрывается большая часть интервью.
В этой статье — типичные вопросы с разбором: как звучит вопрос, рабочий ответ с кодом, объяснение и то, на что смотрит интервьюер.
Python спрашивают у тестировщиков (для автоматизации и работы со скриптами), аналитиков, backend и data-инженеров — почти на любом техническом интервью, где в вакансии упомянут этот язык. Набор тем при этом ограничен: из собеса в собес повторяются одни и те же ловушки и вопросы на понимание, и если разобраться в их механике, закрывается большая часть интервью.
В этой статье — типичные вопросы с разбором: как звучит вопрос, рабочий ответ с кодом, объяснение и то, на что смотрит интервьюер.
Изменяемые аргументы по умолчанию — классическая ловушка Python
Как звучит на собесе: «Что выведет этот код?» — и дают функцию с списком в качестве значения по умолчанию.
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item('яблоко'))
print(add_item('банан'))
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item('яблоко'))
print(add_item('банан'))
Интуитивно ждёшь ['яблоко'] и ['банан'] по отдельности. На деле второй вызов выводит ['яблоко', 'банан'].
Причина: значение аргумента по умолчанию (basket=[]) создаётся один раз, в момент определения функции, а не при каждом вызове. Все вызовы без явного basket работают с одним и тем же списком в памяти — он не пересоздаётся.
Правильный способ — использовать None как маркер и создавать новый список внутри функции:
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
Причина: значение аргумента по умолчанию (basket=[]) создаётся один раз, в момент определения функции, а не при каждом вызове. Все вызовы без явного basket работают с одним и тем же списком в памяти — он не пересоздаётся.
Правильный способ — использовать None как маркер и создавать новый список внутри функции:
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
Теперь каждый вызов без basket получает собственный новый список.
На что смотрит интервьюер: знаете ли вы, что аргументы по умолчанию — изменяемые объекты (списки, словари, множества) — вычисляются один раз при определении функции. Это один из самых частых вопросов на понимание Python, отделяющий тех, кто пользовался языком поверхностно, от тех, кто сталкивался с реальным багом такого рода.
На что смотрит интервьюер: знаете ли вы, что аргументы по умолчанию — изменяемые объекты (списки, словари, множества) — вычисляются один раз при определении функции. Это один из самых частых вопросов на понимание Python, отделяющий тех, кто пользовался языком поверхностно, от тех, кто сталкивался с реальным багом такого рода.
`==` против `is`: сравнение значений и сравнение идентичности
Как звучит на собесе: «В чём разница между == и is в Python?»
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True — одинаковое содержимое
print(a is b) # False — это два разных объекта в памяти
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True — одинаковое содержимое
print(a is b) # False — это два разных объекта в памяти
== сравнивает значения (через метод __eq__) — подходят ли содержимое объектов друг другу. is сравнивает идентичность — один ли это объект в памяти (то же самое, что сравнить id(a) == id(b)).
Частая ловушка — сравнение с None через == вместо is:
# Работает, но не идиоматично
if x == None:
...
# Правильно
if x is None:
...
Частая ловушка — сравнение с None через == вместо is:
# Работает, но не идиоматично
if x == None:
...
# Правильно
if x is None:
...
None в Python — синглтон: он существует в единственном экземпляре, поэтому сравнивать с ним принято через is. Это не только стиль: == можно переопределить в классе, и тогда x == None может повести себя неожиданно, а is None всегда проверяет именно идентичность.
На что смотрит интервьюер: различаете ли вы сравнение по значению и по идентичности, и знаете ли идиому is None / is not None.
На что смотрит интервьюер: различаете ли вы сравнение по значению и по идентичности, и знаете ли идиому is None / is not None.
Списки, кортежи и почему кортежи иногда быстрее
Как звучит на собесе: «Чем список отличается от кортежа? Когда использовать кортеж?»
Список (list) — изменяемая последовательность: можно добавлять, удалять и менять элементы. Кортеж (tuple) — неизменяемая: после создания его содержимое менять нельзя.
my_list = [1, 2, 3]
my_list.append(4) # ок, список изменяемый
my_tuple = (1, 2, 3)
my_tuple[0] = 99 # TypeError: 'tuple' object does not support item assignment
Список (list) — изменяемая последовательность: можно добавлять, удалять и менять элементы. Кортеж (tuple) — неизменяемая: после создания его содержимое менять нельзя.
my_list = [1, 2, 3]
my_list.append(4) # ок, список изменяемый
my_tuple = (1, 2, 3)
my_tuple[0] = 99 # TypeError: 'tuple' object does not support item assignment
Из неизменяемости кортежа следуют практические выводы: кортеж можно использовать как ключ словаря (список — нельзя, потому что ключи должны быть хешируемыми), а создание и перебор кортежа немного быстрее и экономнее по памяти, поскольку интерпретатору не нужно резервировать место под потенциальный рост структуры.
# Кортеж как ключ словаря — работает
coordinates = {(0, 0): 'старт', (1, 1): 'финиш'}
# Список как ключ — ошибка
coordinates = {[0, 0]: 'старт'} # TypeError: unhashable type: 'list'
# Кортеж как ключ словаря — работает
coordinates = {(0, 0): 'старт', (1, 1): 'финиш'}
# Список как ключ — ошибка
coordinates = {[0, 0]: 'старт'} # TypeError: unhashable type: 'list'
На что смотрит интервьюер: понимаете ли вы разницу «изменяемое/неизменяемое» и можете ли объяснить, почему это влияет на использование в качестве ключей словаря, а не просто помните формальное определение.
Списковые включения (list comprehension) вместо циклов
Как звучит на собесе: «Перепиши этот цикл через list comprehension» — дают обычный for, который собирает список.
# Через обычный цикл
squares = []
for x in range(10):
if x % 2 == 0:
squares.append(x ** 2)
# То же самое через list comprehension
squares = [x ** 2 for x in range(10) if x % 2 == 0]
# Через обычный цикл
squares = []
for x in range(10):
if x % 2 == 0:
squares.append(x ** 2)
# То же самое через list comprehension
squares = [x ** 2 for x in range(10) if x % 2 == 0]
Списковое включение читается справа налево по смыслу: «взять x из range(10), отфильтровать по условию x % 2 == 0, применить выражение x ** 2». Это не просто короче — на больших объёмах list comprehension обычно быстрее эквивалентного цикла с .append(), потому что выполняется на уровне интерпретатора без повторных вызовов метода.
Похожий синтаксис работает для словарей и множеств:
squares_dict = {x: x ** 2 for x in range(5)}
unique_lengths = {len(word) for word in ['py', 'python', 'go']}
Похожий синтаксис работает для словарей и множеств:
squares_dict = {x: x ** 2 for x in range(5)}
unique_lengths = {len(word) for word in ['py', 'python', 'go']}
Здесь же стоит упомянуть генераторные выражения — тот же синтаксис, но в круглых скобках, который не создаёт список целиком в памяти, а выдаёт значения по одному:
sum_of_squares = sum(x ** 2 for x in range(1000000))
sum_of_squares = sum(x ** 2 for x in range(1000000))
Для больших последовательностей это экономит память, потому что все миллион квадратов не хранятся одновременно.
На что смотрит интервьюер: умеете ли вы писать питоничный код и понимаете ли разницу между list comprehension (сразу строит список) и генератором (строит значения лениво, по одному).
На что смотрит интервьюер: умеете ли вы писать питоничный код и понимаете ли разницу между list comprehension (сразу строит список) и генератором (строит значения лениво, по одному).
`*args` и `**kwargs`: гибкие аргументы функции
Как звучит на собесе: «Что делают *args и **kwargs? Напиши функцию, которая принимает произвольное число аргументов».
def summarize(*args, **kwargs):
print('Позиционные:', args)
print('Именованные:', kwargs)
summarize(1, 2, 3, name='Анна', age=28)
# Позиционные: (1, 2, 3)
# Именованные: {'name': 'Анна', 'age': 28}
def summarize(*args, **kwargs):
print('Позиционные:', args)
print('Именованные:', kwargs)
summarize(1, 2, 3, name='Анна', age=28)
# Позиционные: (1, 2, 3)
# Именованные: {'name': 'Анна', 'age': 28}
*args собирает произвольное число позиционных аргументов в кортеж. **kwargs собирает произвольное число именованных аргументов в словарь. Названия args и kwargs — это соглашение, а не требование языка: важны именно * и ** перед именем параметра.
Та же звёздочка работает и в обратную сторону — для распаковки при вызове:
def add(a, b, c):
return a + b + c
numbers = [1, 2, 3]
add(*numbers) # эквивалентно add(1, 2, 3)
params = {'a': 1, 'b': 2, 'c': 3}
add(**params) # эквивалентно add(a=1, b=2, c=3)
Та же звёздочка работает и в обратную сторону — для распаковки при вызове:
def add(a, b, c):
return a + b + c
numbers = [1, 2, 3]
add(*numbers) # эквивалентно add(1, 2, 3)
params = {'a': 1, 'b': 2, 'c': 3}
add(**params) # эквивалентно add(a=1, b=2, c=3)
На что смотрит интервьюер: понимаете ли вы механику сбора аргументов в кортеж/словарь и умеете ли применять распаковку — эта тема часто всплывает при разборе чужого кода на декораторах и обёртках функций.
Декораторы: функция, которая оборачивает функцию
Как звучит на собесе: «Напиши декоратор, который логирует время выполнения функции».
import time
from functools import wraps
def log_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f'{func.__name__} выполнилась за {elapsed:.4f} сек')
return result
return wrapper
@log_time
def slow_function():
time.sleep(1)
slow_function()
# slow_function выполнилась за ~1.00 сек
import time
from functools import wraps
def log_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f'{func.__name__} выполнилась за {elapsed:.4f} сек')
return result
return wrapper
@log_time
def slow_function():
time.sleep(1)
slow_function()
# slow_function выполнилась за ~1.00 сек
Декоратор — это функция, которая принимает другую функцию и возвращает новую функцию с добавленным поведением, не изменяя код исходной. @log_time над slow_function — синтаксический сахар, эквивалентный slow_function = log_time(slow_function).
@wraps(func) из functools — важная деталь, которую часто забывают в первой попытке: без неё wrapper подменяет имя и докстринг исходной функции своими, и slow_function.__name__ покажет 'wrapper' вместо 'slow_function'. @wraps сохраняет метаданные оригинальной функции.
На что смотрит интервьюер: понимаете ли вы, что функции в Python — объекты первого класса (их можно передавать и возвращать как любые другие значения), и знаете ли про functools.wraps — его отсутствие часто выдаёт поверхностное знакомство с темой.
@wraps(func) из functools — важная деталь, которую часто забывают в первой попытке: без неё wrapper подменяет имя и докстринг исходной функции своими, и slow_function.__name__ покажет 'wrapper' вместо 'slow_function'. @wraps сохраняет метаданные оригинальной функции.
На что смотрит интервьюер: понимаете ли вы, что функции в Python — объекты первого класса (их можно передавать и возвращать как любые другие значения), и знаете ли про functools.wraps — его отсутствие часто выдаёт поверхностное знакомство с темой.
Частые вопросы про Python на собеседовании
Какие темы Python чаще всего спрашивают на собеседовании? Изменяемые аргументы по умолчанию, разница == и is, список против кортежа, list comprehension, *args/**kwargs, декораторы, работа с исключениями и генераторы. Эти семь тем закрывают большинство вопросов на собеседованиях уровня Junior и Middle.
Спрашивают ли Python у тестировщиков? Да, если в вакансии указана автоматизация тестирования на Python (pytest, Selenium, requests) — тогда спрашивают язык на уровне написания и чтения тестов: функции, циклы, работу с исключениями, иногда декораторы (фикстуры в pytest построены на похожей механике).
Нужно ли знать ООП в Python для собеседования? Базовый уровень — да: классы, __init__, наследование, разница между методом экземпляра и статическим методом. Глубокие темы вроде метаклассов спрашивают редко и только на позициях, требующих архитектурной экспертизы.
Чем список отличается от генератора по памяти? Список хранит все элементы в памяти сразу. Генератор вычисляет и отдаёт значения по одному, не храня всю последовательность целиком — это критично для больших или бесконечных последовательностей.
Как лучше готовиться к Python-собеседованию — теория или практика? Практика. Прочитать про декораторы и решить задачу «напиши декоратор» — разные навыки. Правильная теория проверяется тем, что вы можете написать код руками, а не пересказать определение.
Спрашивают ли Python у тестировщиков? Да, если в вакансии указана автоматизация тестирования на Python (pytest, Selenium, requests) — тогда спрашивают язык на уровне написания и чтения тестов: функции, циклы, работу с исключениями, иногда декораторы (фикстуры в pytest построены на похожей механике).
Нужно ли знать ООП в Python для собеседования? Базовый уровень — да: классы, __init__, наследование, разница между методом экземпляра и статическим методом. Глубокие темы вроде метаклассов спрашивают редко и только на позициях, требующих архитектурной экспертизы.
Чем список отличается от генератора по памяти? Список хранит все элементы в памяти сразу. Генератор вычисляет и отдаёт значения по одному, не храня всю последовательность целиком — это критично для больших или бесконечных последовательностей.
Как лучше готовиться к Python-собеседованию — теория или практика? Практика. Прочитать про декораторы и решить задачу «напиши декоратор» — разные навыки. Правильная теория проверяется тем, что вы можете написать код руками, а не пересказать определение.
Где потренироваться
Отработать эти и другие темы можно на тренажёре Python Arena от Quality Academy — русскоязычная платформа с задачами от простых до уровня реальных собеседований, часть — по мотивам вопросов в Яндекс, Т-Банк, Сбер, Авито, Ozon и VK. Бесплатно доступны все задачи уровня Easy, есть песочница для быстрой проверки кода, AI-ментор разбирает ошибки, а отдельный режим «Собеседование» имитирует реальный технический этап — с таймером и без подсказок.
Короткий чек-лист перед собесом
— Аргумент по умолчанию — изменяемый объект (список, словарь) — вычисляется один раз при определении функции, не при каждом вызове.
— == сравнивает значения, is — идентичность объекта. С None сравнивайте через is.
— Кортеж неизменяем и хешируем — может быть ключом словаря, список — нет.
— List comprehension строит список сразу, генератор — отдаёт значения по одному, экономя память.
— *args собирает позиционные аргументы в кортеж, **kwargs — именованные в словарь; та же звёздочка распаковывает при вызове.
— Декоратор оборачивает функцию новым поведением; не забывайте @wraps(func) из functools.
Если вы уверенно объясняете каждый пункт и можете написать пример без подглядывания — на собеседовании по Python вас будет сложно застать врасплох.
— == сравнивает значения, is — идентичность объекта. С None сравнивайте через is.
— Кортеж неизменяем и хешируем — может быть ключом словаря, список — нет.
— List comprehension строит список сразу, генератор — отдаёт значения по одному, экономя память.
— *args собирает позиционные аргументы в кортеж, **kwargs — именованные в словарь; та же звёздочка распаковывает при вызове.
— Декоратор оборачивает функцию новым поведением; не забывайте @wraps(func) из functools.
Если вы уверенно объясняете каждый пункт и можете написать пример без подглядывания — на собеседовании по Python вас будет сложно застать врасплох.
-------
Полезные ссылки школы
Сайт Quality Academy:
https://quality-academy.ru/main
Telegram-канал:
https://t.me/quality_academy
YouTube-канал:
https://www.youtube.com/@quality_academy
ВКонтакте:
https://vk.com/quality_academy
Канал отзывов учеников (58+ отзывов):
https://t.me/quality_academy_reviews
Задать вопрос менеджеру:
https://t.me/quality_academy_bot
Тест «Подойдёт ли вам тестирование»:
https://quiz.quality-academy.ru
5000+ вопросов с собесов тестировщика — бот-тренажёр:
https://t.me/quality_academy_interview_bot
3 практические задачи и дорожная карта:
https://t.me/quality_academy_tasks_bot
Бесплатные тренажёры — SQL Arena, Playwright Arena, Python Arena:
SQL Arena · Playwright Arena · Python Arena
https://quality-academy.ru/main
Telegram-канал:
https://t.me/quality_academy
YouTube-канал:
https://www.youtube.com/@quality_academy
ВКонтакте:
https://vk.com/quality_academy
Канал отзывов учеников (58+ отзывов):
https://t.me/quality_academy_reviews
Задать вопрос менеджеру:
https://t.me/quality_academy_bot
Тест «Подойдёт ли вам тестирование»:
https://quiz.quality-academy.ru
5000+ вопросов с собесов тестировщика — бот-тренажёр:
https://t.me/quality_academy_interview_bot
3 практические задачи и дорожная карта:
https://t.me/quality_academy_tasks_bot
Бесплатные тренажёры — SQL Arena, Playwright Arena, Python Arena:
SQL Arena · Playwright Arena · Python Arena