Изменяемые и неизменяемые типы в Python на собеседовании
Список типов помнят все, а спрашивают про следствия: изменяемый аргумент по умолчанию и кортеж со списком внутри. Разбираем, что проверяет вопрос.
Список изменяемых типов запоминается за минуту, и на нём вопрос не заканчивается. Дальше идут следствия: аргумент по умолчанию, который помнит прошлый вызов, кортеж, содержимое которого меняется, и ключ словаря, который вдруг не подходит. Все три выводятся из одного правила, и ниже разобрано, из какого.
Всё, что сказано о поведении, относится к Python 3.
Три свойства объекта §
Модель данных языка начинается с утверждения, которое стоит взять за основу ответа: у каждого объекта есть идентичность, тип и значение. Идентичность не меняется никогда, и документация предлагает думать о ней как об адресе объекта в памяти. Тип тоже не меняется. Меняться может только значение, и то не всегда.
Ключевая строка звучит дальше и снимает половину вопросов темы: изменяемость объекта определяется его типом. Числа, строки и кортежи неизменяемы; словари и списки изменяемы. Изменяемость задаёт тип, поэтому она предсказуема заранее, что бы с конкретным объектом ни делали.
Отсюда выводится и различие между is и ==: is сравнивает идентичность, то есть тот же самый это объект или нет; == сравнивает значения. Для неизменяемых объектов разница часто незаметна.
Аргумент по умолчанию вычисляется один раз §
Инженер, ведущий открытые тестовые интервью с джуниорами, включает этот пример в набор ловушек по теме. Пример показательный: «странность» здесь ровно нулевая.
Правило записано в справочнике языка прямо: значения параметров по умолчанию вычисляются слева направо при выполнении определения функции. То есть выражение вычисляется один раз, в момент определения, и одно и то же заранее вычисленное значение используется при каждом вызове.
Дальше всё следует само. Если значение по умолчанию — изменяемый объект, например список, и функция его изменяет, скажем добавляет элемент, то изменяется само значение по умолчанию. Документация добавляет: обычно это не то, что имелось в виду. При втором вызове функция получит список, в котором уже лежит результат первого.
Обходной путь тоже предписан документацией: значением по умолчанию ставят None и явно проверяют его в теле, создавая новый список внутри. Тогда каждый вызов получает свой объект.
Связь помогает запомнить правило: в тот же момент, при определении функции, вычисляются и выражения декораторов.
Неизменяемый контейнер с изменяемым содержимым §
Учебное изложение заканчивается списком типов, а следующий случай в него не укладывается. Кортеж неизменяем, но если внутри него лежит список, содержимое кортежа наблюдаемо меняется, и противоречия в этом нет.
Документация разбирает случай прямо и формулирует вывод, который стоит уметь воспроизвести: значение неизменяемого контейнера, содержащего ссылку на изменяемый объект, может измениться, когда изменится значение последнего; однако контейнер по-прежнему считается неизменяемым, потому что набор объектов, которые он содержит, изменить нельзя. И следом: неизменяемость — не строго то же самое, что неизменяемое значение, это тоньше.
Разгадка в том, что именно хранит контейнер. Кортеж хранит ссылки, и неизменяемость означает, что нельзя заменить одну ссылку на другую. То, куда эти ссылки ведут, кортежу не подчиняется.
Тот же механизм объясняет ещё две ловушки из того же набора:
- Умножение списка. Выражение вида
[[]] * 3создаёт не три независимых списка, а три ссылки на один и тот же объект. Изменение через любую из них видно через все. +=против=. Для спискаa += [3]изменяет объект на месте, аa = a + [3]создаёт новый объект и связывает имя с ним. Разница незаметна, пока на прежний объект не смотрит кто-то ещё, например другое имя или элемент кортежа.
Что можно положить в ключ словаря §
Последний шаг применяет уже сказанное. Ключом словаря может быть только хешируемый объект: словарь использует значение хеша внутри, и оно должно не меняться в течение всей жизни объекта.
Правило для контейнеров словарь языка формулирует одной строкой, и она содержит весь ответ: большинство неизменяемых встроенных объектов хешируемы; изменяемые контейнеры, списки и словари, не хешируемы; а неизменяемые контейнеры вроде кортежей и frozenset хешируемы только в том случае, если хешируемы их элементы.
Отсюда ответ на вопрос про кортеж со списком внутри выводится в одну строку: кортеж неизменяем, но его элемент — список, а список не хешируем; значит, и кортеж не хешируем, и ключом словаря быть не может.
Полезно назвать и причину, по которой правило именно такое; она возвращает к предыдущей секции. Если бы такой кортеж был ключом, изменение списка внутри поменяло бы значение хеша, и запись перестала бы находиться там, где её положили. Постоянство хеша — условие работоспособности словаря.
Проверьте себя
Сначала проговорите ответ, потом сверьтесь с текстом.
Чем список отличается от кортежа?
Что проверяют: называете вы разницу как свойство типа или как разницу в синтаксисе и «предназначении».
Частая ошибка: ответить «кортеж — для фиксированных данных, список — для меняющихся». Такой ответ описывает стиль использования и ничего не говорит о том, что можно и нельзя сделать с объектом.
Что вернёт функция при втором вызове, если её значение по умолчанию — пустой список?
Что проверяют: различаете вы момент определения и момент вызова.
Частая ошибка: ответить «то же, что и при первом: пустой список плюс элемент». Список создан один раз при определении, и он же используется повторно.
Можно ли изменить кортеж?
Что проверяют: понимаете вы, что неизменяемость относится к набору ссылок.
Частая ошибка: ответить «нет, никогда» и не суметь объяснить наблюдаемое изменение содержимого. Сам кортеж действительно не меняется, меняется объект, на который он ссылается.
Подойдёт ли кортеж со списком внутри как ключ словаря?
Что проверяют: выводите вы ответ из правила про хешируемость элементов или отвечаете по первому признаку: «кортеж неизменяем, значит подойдёт».
Частая ошибка: ответить «да, кортеж же неизменяемый». Неизменяемость контейнера необходима, но не достаточна: его элементы тоже должны быть хешируемы.
Что дальше
В Telegram-канале разбираем вопросы с собеседований.
Проверьте эти темы вслух
В тренажёре вы отвечаете голосом, а разбор показывает, где ответ просел.