В КУРСЕ?

Разбираемся в теме

Функция Python помнит прошлый вызов: откуда берутся лишние элементы

Функция добавляет элемент в список и возвращает результат. Первый вызов выглядит правильным, а второй неожиданно содержит данные первого. Причина может находиться в значении аргумента по умолчанию. Это небольшой пример, который помогает понять объекты, изменение данных и время выполнения определения функции. Разобрать его можно сначала на бумаге, без установки дополнительных пакетов и создания большого проекта.

Значение по умолчанию создаётся при выполнении определения

Если пустой список указан непосредственно как значение параметра по умолчанию, соответствующий объект создаётся при выполнении определения функции. Он не возникает заново при каждом последующем вызове. Поэтому вызовы, в которых этот аргумент пропущен, могут получать один и тот же список. Добавление элемента изменяет сам объект. Следующий вызов обращается к уже изменённому состоянию и видит прежнее содержимое. Здесь нет скрытой базы данных или особого запоминания со стороны языка. Поведение следует из того, когда создан объект и какие ссылки продолжают на него указывать. Например, первый вызов добавляет название груши, второй название яблока. При общем списке второй результат включает оба названия. Если по замыслу каждый вызов должен начинать отдельную подборку, такое поведение будет ошибкой.

Создавайте новый контейнер там, где он действительно нужен

Распространённый способ выразить отсутствие переданного списка состоит в значении None. Внутри функции проверяют именно этот случай и создают новый пустой список. Тогда отдельные вызовы без аргумента получают отдельные контейнеры. При этом важно не заменять проверку отсутствия проверкой пустоты без размышления. Пустой список может быть осознанно передан вызывающей стороной. Если договорённость допускает его изменение, подмена новым объектом нарушит ожидаемую связь. Следовательно, решение зависит не только от синтаксиса, но и от смысла параметра. Если None сам по себе является допустимым значением данных, для обозначения отсутствия может понадобиться другой подход. Основная идея остаётся прежней: отсутствие аргумента и конкретное переданное значение должны различаться однозначно.

Уточните, кто владеет изменением

Исправление значения по умолчанию не отменяет другой вопрос: должна ли функция менять список, который ей передали явно? Иногда это ожидаемое поведение, иногда требуется вернуть отдельный результат. Такое решение нужно описать в договорённости функции и проверить на примерах. Копирование внешнего списка создаёт отдельный внешний контейнер, но поверхностная копия не делает независимыми все вложенные изменяемые объекты. Поэтому нельзя обещать полную изоляцию данных одним общим словом копия. Для первого упражнения удобно использовать простые строки и не смешивать эту тему с вложенными структурами. Проверять полезно не только один успешный вызов. Последовательность нескольких вызовов обнаруживает накопление состояния, которое незаметно в отдельном примере. Именно ожидаемые отношения между вызовами определяют, какой результат считать правильным.

Попробуйте на практике

Проследите два варианта функции, добавляющей название предмета в список.

  1. На бумаге обозначьте один список, созданный как значение по умолчанию. Два раза добавьте в него разные названия, изображая последовательные вызовы.
  2. Запишите, что увидит второй вызов, если аргумент списка оба раза пропущен. Объясните сохранение первого названия.
  3. Нарисуйте вариант, где при отсутствии аргумента внутри каждого вызова создаётся новый список. Сравните результаты.
  4. Отдельно рассмотрите явно переданный пустой список. Укажите, должна ли функция изменять его согласно выбранной договорённости.

Как проверить результат. Общий список накапливает оба названия, отдельные списки не смешивают вызовы. Поведение для явно переданного контейнера описано отдельно от значения по умолчанию.

Частые вопросы

Любое значение по умолчанию опасно?

Нет. Проблема связана с изменяемым общим объектом и ожиданиями программы. Сам механизм значений по умолчанию полезен и предсказуем.

Достаточно проверить только первый вызов?

Нет. Ошибка накопления состояния может проявиться позже. Нужны последовательные вызовы и отдельный случай с явно переданным аргументом.

Самостоятельный разбор темы. Содержание конкретной обучающей программы здесь не представлено.

Зарегистрируйтесь, чтобы уточнить возможность доступа к этому материалу

Зарегистрироваться
← К списку материалов