// python interview / basics

Вопросы на собеседовании: Основы языка и типы

Объектная модель, is vs ==, числа и строки, импорты, память и GC — база, с которой начинается почти каждый собес. Здесь — топ-15 по частоте на реальных собесах: у первых вопросов открыт полный разбор, у остальных — устный эталон. Весь банк темы (27 вопросов) с разборами — в приложении.

Открыть тему в приложении каждый день бесплатно: 3 эталона и 3 проверки арбитром

junior: база, с которой начинают

Расскажи, что имеют в виду, когда говорят «в Python всё — объект»? Из чего вообще состоит объект?

как ответить В Python любая сущность — число, строка, функция, класс, модуль — это объект: экземпляр какого-то типа, у которого есть три вещи — идентичность (id), тип (type) и значение. Даже классы — объекты типа type, а функции можно хранить в переменных и передавать как аргументы наравне с числами и строками. В отличие от C или Java, здесь нет «примитивных» типов, живущих отдельно от объектной модели.

разбор

id() в CPython возвращает адрес объекта в памяти на время его жизни — это деталь реализации, а не языковая гарантия, но её удобно использовать как «отпечаток» идентичности. type() возвращает класс, от которого объект создан, и type(obj) is SomeClass — валидный способ проверить точный тип (в отличие от isinstance, которая ещё смотрит наследование). Значение — это данные, которые объект хранит внутри.

Типичная ловушка новичков — думать, что int или bool в Python «примитив», как в C/Java, и поэтому не объект. На деле True.bit_length() и (5).__add__ работают ровно потому, что это полноценные объекты с методами.

Частый follow-up: «а зачем тогда id(), если есть переменные?» — потому что имя переменной это просто ссылка на объект, а не сам объект; одно и то же значение может быть доступно под разными именами.

чтобы прозвучать сильнее Упомяни, что в CPython у каждого объекта есть служебный заголовок (ob_refcnt для подсчёта ссылок, ob_type — указатель на тип) — это не публичный API, но объясняет, откуда вообще берётся id() и почему сборка мусора в CPython по умолчанию основана на refcounting, а не только на трассировке.

python-basics-001 · junior · high

Чем is отличается от ==, и когда что использовать?

как ответить is сравнивает идентичность — один и тот же объект в памяти это или нет, по сути сравнение id(). == вызывает метод __eq__ и сравнивает значения по логике, которую задаёт тип объекта. Для None, True, False и подобных синглтонов используют is, потому что они существуют в единственном экземпляре; для проверки «равны ли значения» у чисел, строк, списков — ==.

разбор

Классический пример: [1, 2] == [1, 2] — True, потому что списки сравниваются поэлементно, но [1, 2] is [1, 2] — False, потому что это два разных объекта в памяти. Если не переопределить __eq__, он по умолчанию наследуется от object и падает обратно на сравнение идентичности — то есть для собственного класса без __eq__ == и is дают одинаковый результат, и это тоже нередко путает.

is работает быстрее — это сравнение указателей, — но скорость не повод его выбирать вместо ==; выбор всегда про семантику: «тот же объект» против «то же значение».

Типичный follow-up: «а можно ли по is сравнивать числа?» — формально можно, но полагаться на это опасно из-за интернирования, это отдельная тема.

чтобы прозвучать сильнее Добавь, что дефолтная реализация __eq__ от object — это как раз fallback на сравнение по идентичности, и что != автоматически получает противоположное поведение из __eq__ начиная с Python 3 — руками __ne__ почти никогда не пишут.

python-basics-002 · junior · high

Почему для проверки на None принято писать x is None, а не x == None?

как ответить None — единственный экземпляр своего типа NoneType, гарантированный синглтон на уровне языка, поэтому сравнение через is надёжно, быстро и не зависит от того, как тип слева переопределил __eq__. Если написать x == None, а x — объект с нестандартным __eq__ (который всегда возвращает True, кидает исключение или, как у numpy-массивов, возвращает не bool, а массив), проверка сломается непредсказуемо.

полный разбор и проверка ответа арбитром — в приложении python-basics-003 · junior · high

Что выведет этот код и почему? И заодно скажи, чем в этом плане int отличается от float в Python.

a = 0.1 + 0.2
print(a == 0.3)
print(a)

как ответить Первый print даст False, второй — 0.30000000000000004: float — это IEEE 754 double, 0.1 и 0.2 не представимы точно в двоичной системе (как 1/3 в десятичной), поэтому при сложении накапливается ошибка округления. int в Python устроен иначе — это произвольная точность: число хранится не в фиксированных 64 битах, а как динамически растущий набор 'цифр', поэтому переполнения нет и 2**1000 считается точно, просто ценой памяти и скорости на больших значениях.

полный разбор и проверка ответа арбитром — в приложении python-basics-004 · junior · high

Чем оператор / отличается от // в Python, и какая тут есть засада с отрицательными числами?

как ответить / — это true division, в Python 3 всегда возвращает float, даже если оба операнда int. // — floor division: результат округляется вниз, в сторону минус бесконечности, а не усечением к нулю, как в C. Поэтому -7 // 2 даёт -4, а не -3, и это регулярно ловит тех, кто ожидает 'обычное целочисленное деление'.

полный разбор и проверка ответа арбитром — в приложении python-basics-005 · junior · high

Что выведет round(2.5) и round(3.5)? Почему, и когда вместо round() стоит взять Decimal?

print(round(2.5))
print(round(3.5))

как ответить round(2.5) даст 2, а round(3.5) даст 4 — Python округляет по банковскому правилу round half to even: ровно посередине округляет к ближайшему чётному, а не всегда вверх, как учили в школе. Это статистически честнее при массовом округлении, но контринтуитивно для одного числа. Для денег и финансовой логики это правило не подходит — там берут decimal.Decimal с явным quantize и ROUND_HALF_UP, потому что нужна детерминированная, юридически предсказуемая арифметика, а не банковское округление float.

полный разбор и проверка ответа арбитром — в приложении python-basics-006 · junior · high

Расскажи, что делает объект булево истинным или ложным в Python — как работает truthiness и в каком порядке питон смотрит на __bool__ и __len__?

class Empty:
    def __len__(self):
        return 0

print(bool(Empty()))

как ответить Python вызывает __bool__, если он определён, и берёт его возвращаемое значение. Если __bool__ не определён, но есть __len__ — объект falsy при нулевой длине и truthy иначе. Если нет ни того ни другого — объект всегда truthy. Отсюда falsy по умолчанию: None, False, 0, 0.0, пустые строка/список/словарь/множество.

полный разбор и проверка ответа арбитром — в приложении python-basics-007 · junior · high

Чем if x: отличается от if x is not None:? Когда эта разница реально роняет прод?

как ответить if x: проверяет truthiness — сработает как False и на None, и на 0, '', [], {}, False. if x is not None: проверяет конкретно идентичность с синглтоном None, пропуская валидные, но falsy значения. Баг типичен, когда 0, '' или пустой список — легитимный ответ, а код по ошибке трактует его как «отсутствует значение».

полный разбор и проверка ответа арбитром — в приложении python-basics-008 · junior · high

Опиши по шагам, что происходит в интерпретаторе, когда ты пишешь import foo в первый раз в программе.

import sys
import json

before = 'json' in sys.modules
import json as json2
print(before, json2 is sys.modules['json'])

как ответить Интерпретатор сначала проверяет sys.modules — если модуль там уже есть, просто возвращает готовый объект без повторного выполнения кода. Если нет, ищет файл по sys.path, создаёт пустой module-объект, кладёт его в sys.modules ещё до выполнения (это важно для циклических импортов), затем исполняет код модуля построчно в его namespace, и после этого имя foo привязывается в текущем namespace к этому объекту.

полный разбор и проверка ответа арбитром — в приложении python-basics-009 · junior · high

Расскажи, как Python управляет памятью объектов на уровне рантайма. Что реально происходит при вызове del x?

как ответить В CPython у каждого объекта есть счётчик ссылок (refcount) — он растёт при каждом новом связывании имени, элементе контейнера или аргументе функции и уменьшается при выходе ссылки из области видимости. Когда счётчик доходит до нуля, объект немедленно освобождается — это делает управление памятью в CPython детерминированным. del x не удаляет объект напрямую: он убирает имя x из текущего пространства имён и уменьшает refcount объекта, на который оно указывало; сам объект живёт, пока на него есть другие ссылки.

полный разбор и проверка ответа арбитром — в приложении python-basics-010 · junior · high

Что выведет код? Почему and и or в Python возвращают не обязательно bool, и когда правый операнд вообще не вычисляется?

def side_effect():
    print("called")
    return 42


print("" or "fallback")
print("ready" and 0)
print(None and side_effect())

как ответить and и or возвращают один из исходных операндов, а не обязательно True или False. or возвращает первый truthy-операнд либо последний, а and — первый falsy-операнд либо последний. Правую часть вычисляют только тогда, когда она нужна, поэтому вывод будет fallback, 0, None, а called не появится.

полный разбор и проверка ответа арбитром — в приложении python-basics-026 · junior · high

Что значит, что строки в Python неизменяемы, и какие практические следствия у этого?

как ответить Строка — неизменяемый объект: любые «изменения» — replace, upper, конкатенация — создают новую строку, а не правят существующую. s += 'x' — это тоже создание нового объекта и перепривязка имени s на него. Следствия: строки хешируемы и годятся в ключи dict, их безопасно передавать куда угодно, а классическая ошибка новичка — вызвать s.upper() и не присвоить результат.

полный разбор и проверка ответа арбитром — в приложении python-basics-011 · junior · medium

middle: где отделяют уверенных

Зачем нужна конструкция if __name__ == "__main__": и что будет с кодом внутри и вне этого блока, если файл не запускать напрямую, а импортировать?

как ответить Атрибут __name__ модуля интерпретатор выставляет сам: '__main__', если файл — точка входа процесса, и полное dotted-имя модуля, если файл был импортирован. Код вне этого блока выполняется в обоих случаях одинаково, а сам блок фильтрует только то, что должно запускаться исключительно при прямом старте — CLI-парсинг, демо-вызовы, запуск сервера — чтобы импорт файла не давал побочных эффектов.

полный разбор и проверка ответа арбитром — в приложении python-basics-013 · middle · high

Что такое циклический импорт, из-за чего он обычно возникает в реальном проекте, и как ты его обычно разрываешь?

как ответить Циклический импорт — когда модуль A импортирует B, а B прямо или транзитивно импортирует A обратно. Python не падает на самом факте цикла — модуль кладётся в sys.modules ещё до выполнения своего кода, поэтому падает не импорт модуля целиком, а обращение к конкретному имени, которого во втором заходе ещё нет в частично выполненном namespace. Разрывают его переносом импорта внутрь функции, выносом общей части в третий модуль, либо пересмотром архитектуры — цикл на уровне модулей обычно значит, что они знают друг о друге слишком много.

полный разбор и проверка ответа арбитром — в приложении python-basics-014 · middle · high

Циклические ссылки — почему они проблема для подсчёта ссылок, и как Python с ними справляется? Что делает модуль gc?

как ответить Если два объекта ссылаются друг на друга (например, parent/child без weakref), их refcount никогда сам не опустится до нуля, даже когда оба недостижимы из кода — чистый reference counting циклы не освобождает. Для этого в CPython есть отдельный поколенческий сборщик (модуль gc): он периодически обходит контейнерные объекты, находит недостижимые циклы и освобождает их. Управление — gc.collect() вручную, gc.set_threshold() для порогов, gc.disable() — осознанный отказ ценой утечек на циклах.

полный разбор и проверка ответа арбитром — в приложении python-basics-015 · middle · high

Соседние темы того же собеса