Вопросы по Python на собеседовании: Junior — часть 1/3 с разбором ответов
Вопросы по Python на собеседовании: Junior — часть 1/3 с разбором ответов
TL;DR: 8 вопросов, которые задают на Junior Python-собеседованиях — с разбором ответов, примерами кода и типичными ошибками. Mutable vs immutable,
isvs==, list vs tuple,*args/**kwargs, comprehensions, LEGB, декораторы и__init__vs__new__.
Как пользоваться: Открой перед собеседованием → пройдись по каждому вопросу → проверь, можешь ли ответить на follow-up. Слабые места — в тренажёр.
Эта подборка — не пересказ документации. Это вопросы, которые реально задают на собеседованиях по Python в 2024–2026 годах — по отзывам с Glassdoor, Хабра и Reddit. Python уверенно держит первое место по количеству технических собеседований — 23% всех интервью по данным анализа 9 247 собесов.
Для каждого вопроса: что интервьюер на самом деле проверяет, как ответить, и где большинство кандидатов ошибается.
Содержание
Junior
- 1. Чем отличаются mutable и immutable типы?
- 2. В чём разница между is и ==?
- 3. list vs tuple — когда что использовать?
- 4. Что такое *args и **kwargs?
- 5. List comprehension — синтаксис и подводные камни
- 6. Как работает область видимости переменных?
- 7. Что такое декоратор?
- 8. Чем отличается init от new?
1. Чем отличаются mutable и immutable типы? {#1-chem-otlichayutsya-mutable-i-immutable-tipy}
Интервьюер проверяет: понимаешь ли ты, как Python работает с памятью на базовом уровне.
Immutable (неизменяемые) — объекты, которые нельзя изменить после создания. При «изменении» создаётся новый объект:
int,float,boolstrtuple,frozensetbytes
Mutable (изменяемые) — можно менять содержимое без создания нового объекта:
list,dict,setbytearray- Любой пользовательский класс (по умолчанию)
# Immutable — создаётся новый объект
a = "hello"
print(id(a)) # 140234866534960
a += " world"
print(id(a)) # 140234866535024 — другой id!
# Mutable — тот же объект
b = [1, 2, 3]
print(id(b)) # 140234866601280
b.append(4)
print(id(b)) # 140234866601280 — тот же id
Почему это важно на практике: mutable объект как аргумент по умолчанию — классическая ловушка:
# BAD — список создаётся один раз при определении функции
def add_item(item, items=[]):
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] — сюрприз!
# GOOD — идиоматичный паттерн
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
Типичная ошибка: говорить «строки неизменяемые, потому что Python так решил». На самом деле это решение влияет на хешируемость (только immutable объекты могут быть ключами dict и элементами set) и на потокобезопасность.
Follow-up: Можно ли изменить элемент внутри tuple? — Да, если элемент сам mutable: t = ([1, 2],); t[0].append(3) работает.
2. В чём разница между is и ==? {#2-v-chyom-raznitsa-mezhdu-is-i-}
Интервьюер проверяет: различаешь ли ты идентичность объекта и равенство значений.
==сравнивает значения (вызывает метод__eq__)isсравнивает идентичность — указывают ли две переменные на один и тот же объект в памяти (id())
a = [1, 2, 3]
b = [1, 2, 3]
a == b # True — значения одинаковые
a is b # False — разные объекты в памяти
c = a
a is c # True — один и тот же объект
Нюанс с интернированием (string/integer caching): CPython кеширует маленькие целые числа от -5 до 256 и некоторые строки:
x = 256
y = 256
x is y # True — CPython кеширует
x = 257
y = 257
x is y # False (в большинстве случаев)
Когда использовать is: только для сравнения с None, True, False и синглтонами. Это рекомендация PEP 8.
# GOOD
if value is None:
...
# BAD
if value == None:
...
Типичная ошибка: использовать is для сравнения строк или чисел — может работать в REPL из-за кеширования, но сломается в продакшене.
Follow-up: Что вернёт "hello" is "hello"? — Зависит от реализации. CPython может интернировать, но полагаться на это нельзя.
3. list vs tuple — когда что использовать? {#3-list-vs-tuple--kogda-chto-ispolzovat}
Интервьюер проверяет: умеешь ли ты выбирать структуру данных осознанно, а не по привычке.
- list — изменяемая последовательность. Для коллекций однородных элементов, где нужны добавление/удаление. - tuple — неизменяемая последовательность. Для фиксированных наборов, часто разнородных данных.
# list — набор пользователей (может расти)
users = ["Alice", "Bob", "Charlie"]
users.append("Diana")
# tuple — координаты точки (фиксированная структура)
point = (10.5, 20.3)
# point[0] = 15.0 # TypeError!
Ключевые различия:
- Производительность: tuple создаётся быстрее и занимает меньше памяти. CPython кеширует маленькие tuple. - Хешируемость: tuple (если все элементы immutable) можно использовать как ключ dict или элемент set. list — нельзя. - Семантика: tuple — это запись (record) с фиксированной структурой. list — это коллекция.
import sys
lst = [1, 2, 3]
tpl = (1, 2, 3)
sys.getsizeof(lst) # 120 байт
sys.getsizeof(tpl) # 64 байта
Типичная ошибка: говорить «tuple быстрее, поэтому всегда используй tuple». Выбор зависит от семантики, а не только от скорости. Если нужно менять — list. Если данные фиксированы — tuple.
Follow-up: Что такое NamedTuple и зачем он нужен?
Мы разобрали типы данных и сравнение объектов. Дальше — аргументы функций и синтаксический сахар Python. Это то, что проверяет умение писать идиоматичный код.
4. Что такое *args и **kwargs? {#4-chto-takoe-args-i-kwargs}
Интервьюер проверяет: знаешь ли ты механизм передачи произвольного числа аргументов.
*args собирает позиционные аргументы в tuple. **kwargs собирает именованные аргументы в dict.
def example(*args, **kwargs):
print(f"args: {args}") # tuple
print(f"kwargs: {kwargs}") # dict
example(1, 2, 3, name="Alice", age=30)
# args: (1, 2, 3)
# kwargs: {'name': 'Alice', 'age': 30}
Порядок аргументов в сигнатуре:
def func(positional, *args, keyword_only, **kwargs):
pass
# positional — обычный аргумент
# *args — произвольные позиционные
# keyword_only — после *args, только по имени
# **kwargs — произвольные именованные
Практическое применение: декораторы, обёртки, логирование — когда нужно пробросить аргументы «как есть»:
def log_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
Типичная ошибка: путать распаковку (*) в вызове и сборку (*) в определении. func(*[1, 2]) — распаковка. def func(*args) — сборка.
Follow-up: Что будет, если передать *args после **kwargs в определении функции?
5. List comprehension — синтаксис и подводные камни {#5-list-comprehension--sintaksis-i-podvodnye-kamni}
Интервьюер проверяет: пишешь ли ты идиоматичный Python или «Java на Python».
# Цикл
squares = []
for x in range(10):
squares.append(x ** 2)
# Comprehension — быстрее и читабельнее
squares = [x ** 2 for x in range(10)]
# С фильтром
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]
# Dict comprehension
word_lengths = {w: len(w) for w in ["hello", "world"]}
# Set comprehension
unique_lengths = {len(w) for w in ["hello", "world", "hi"]}
Когда НЕ использовать: если логика сложнее одной строки — comprehension превращается в нечитаемую кашу. Правило: если нужен if/else И вложенный цикл — лучше обычный цикл.
# BAD — сложно читать
result = [f(x) for x in items if x > 0 for y in x.children if y.active]
# GOOD — обычный цикл для сложной логики
result = []
for x in items:
if x > 0:
for y in x.children:
if y.active:
result.append(f(x))
Типичная ошибка: использовать comprehension для побочных эффектов: [print(x) for x in items] — создаёт бесполезный список None. Используй обычный цикл.
Follow-up: Чем list comprehension отличается от generator expression?
6. Как работает область видимости переменных? {#6-kak-rabotaet-oblast-vidimosti-peremennykh}
Интервьюер проверяет: понимаешь ли ты, где Python ищет переменные.
Python использует правило LEGB:
- Local — внутри текущей функции
- Enclosing — во вложенных функциях (замыкания)
- Global — на уровне модуля
- Built-in — встроенные имена (print, len, range)
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # "local"
inner()
print(x) # "enclosing"
outer()
print(x) # "global"
global и nonlocal:
count = 0
def increment():
global count # без этого — UnboundLocalError
count += 1
def outer():
value = 10
def inner():
nonlocal value # ссылка на enclosing scope
value += 1
inner()
print(value) # 11
Типичная ошибка: не понимать, почему UnboundLocalError возникает при x += 1 без global. Python при компиляции функции видит присваивание x = и считает x локальной — до того, как она была инициализирована.
Follow-up: Что произойдёт, если назвать переменную list или print? — Она перекроет built-in в текущем scope.
Базовые механизмы языка позади. Два последних вопроса Junior-уровня — про декораторы и создание объектов. На этом собесы для джунов часто заканчиваются.
7. Что такое декоратор? {#7-chto-takoe-dekorator}
Интервьюер проверяет: понимаешь ли ты функции как объекты первого класса.
Декоратор — это функция, которая принимает другую функцию и возвращает изменённую версию. Синтаксис @ — это сахар.
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before call")
result = func(*args, **kwargs)
print("After call")
return result
return wrapper
@my_decorator
def say_hello(name):
print(f"Hello, {name}!")
# Эквивалентно:
# say_hello = my_decorator(say_hello)
say_hello("Alice")
# Before call
# Hello, Alice!
# After call
Важно: всегда используй functools.wraps — иначе теряются __name__, __doc__ и другие метаданные оригинальной функции:
from functools import wraps
def my_decorator(func):
@wraps(func) # сохраняет метаданные func
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Типичная ошибка: забыть return func(*args, **kwargs) внутри wrapper — декоратор «проглотит» возвращаемое значение.
Follow-up: Можно ли повесить несколько декораторов на одну функцию? В каком порядке они применяются?
8. Чем отличается init от new? {#8-chem-otlichaetsya-init-ot-new}
Интервьюер проверяет: понимаешь ли ты двухэтапное создание объектов в Python.
__new__— создаёт новый экземпляр класса (аллокация памяти). Вызывается до__init__.__init__— инициализирует уже созданный экземпляр (заполняет атрибуты).
class MyClass:
def __new__(cls, *args, **kwargs):
print("__new__ called")
instance = super().__new__(cls)
return instance
def __init__(self, value):
print("__init__ called")
self.value = value
obj = MyClass(42)
# __new__ called
# __init__ called
Когда нужен __new__: почти никогда для обычного кода. Основные случаи:
- Singleton паттерн:
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
- Наследование от immutable типов (str, int, tuple) — __init__ не может изменить уже созданный immutable объект.
Типичная ошибка: переопределять __new__ там, где достаточно __init__. Для 99% задач нужен только __init__.
Follow-up: Что произойдёт, если __new__ вернёт экземпляр другого класса?
Итого Junior: 8 вопросов. Фокус — типы данных, операторы сравнения, функции, scope, декораторы. Если уверенно отвечаешь на follow-up — ты готов к Junior-собеседованию по Python.
Чего НЕ спрашивают на Junior
- Алгоритмы сортировки наизусть — могут попросить решить простую задачу, но не заставят писать quicksort.
- Metaclasses и дескрипторы — это Senior-территория.
- Asyncio — от джуна не ждут знания асинхронности.
- Внутреннее устройство CPython — GIL, garbage collection — это Middle+.
Как готовиться к Junior Python-собеседованию
- Пройдись по всем 8 вопросам — если не можешь объяснить своими словами, разберись глубже.
- Напиши код руками — не читай ответы, а пиши. Открой REPL и проверь каждый пример.
- Прорешай 10-15 задач на LeetCode Easy — list, dict, string операции.
- Подготовь пет-проект — хотя бы один, который можешь показать и обсудить.
Как попробовать Sobes AI
Sobes AI — AI-помощник для технических собеседований. Как он поможет с этими вопросами:
- Скачай приложение на sobesai.app
- Запусти тренировку — AI задаёт вопросы из реальных собеседований
- Получи обратную связь — что ответил хорошо, где слабые места
- Пройди mock-собеседование — имитация реального интервью с follow-up вопросами
- Используй на реальном собесе — AI подсказывает в реальном времени
Готовитесь к собеседованию?
Sobes AI слушает вопросы интервьюера и генерирует ответы в реальном времени.
Скачать Sobes AI