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