Перейти к содержанию
Языки

Изменяемые аргументы по умолчанию в Python: ошибка и исправление

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

Автор: Опубликовано

4 мин чтенияРедакционный разборОбновлено
  • Python
  • аргументы по умолчанию
  • изменяемость
  • собеседование
Главная мысль

Если None не является допустимыми данными, используйте его как признак отсутствующего аргумента, проверяйте через is None и создавайте список внутри функции. Отдельно решите, нужно ли изменять или копировать явно переданный список.

Что останется в списке после двух вызовов

Запустите пример в новом процессе Python. Вывод: ['SQL'], затем ['SQL', 'Go'], затем True и снова ['SQL', 'Go']. Функция возвращает сам список history, а не снимок его содержимого. first и second ссылаются на один объект, поэтому второй вызов меняет и наблюдаемое значение first.

Список по умолчанию принадлежит конкретному объекту функции. Повторное выполнение def создаёт новую функцию с новым значением по умолчанию. Это важно для вложенных функций и экспериментов в notebook: повторный запуск определения может сбросить состояние и замаскировать проблему.

def remember(topic, history=[]):
    history.append(topic)
    return history

first = remember("SQL")
print(first)
second = remember("Go")
print(second)
print(first is second)
print(first)

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

Исправленная функция печатает ['SQL'] ['Go'] False, затем ['Python'] True. Вызовы без history получают независимые списки. Когда вызывающий код явно передаёт owned, функция изменяет и возвращает именно этот объект — такой контракт выбран в примере.

Не заменяйте условие на if not history или history = history or []. Пустой список ложен в логическом контексте, но может быть намеренно переданным объектом. Его замена незаметно меняет поведение API. Если обещано не изменять данные вызывающего кода, явно копируйте переданную коллекцию и описывайте это решение.

def remember(topic, history=None):
    if history is None:
        history = []
    history.append(topic)
    return history

first = remember("SQL")
second = remember("Go")
print(first, second, first is second)
owned = []
result = remember("Python", owned)
print(owned, result is owned)

Выбирайте значение по умолчанию под контракт

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

Если None является допустимым входом, отдельный sentinel object() позволяет отличить пропущенный аргумент от явного None. Сравнивайте его по идентичности. Аналогично рассуждайте о временной метке или случайном идентификаторе по умолчанию: значение нужно при создании функции или заново при каждом вызове?

Выбор значения по умолчанию
ТребованиеПриёмПроверка
Новый список при пропущенном аргументеhistory=None; создание внутриРезультаты двух вызовов — разные объекты
Сохранить явно переданный пустой списокif history is NoneВозвращён исходный объект
None — допустимые данныеОтдельный sentinel object()Пропуск отличается от явного None
Новый список для каждого dataclassfield(default_factory=list)Экземпляры не разделяют поле

Для поля dataclass используйте default_factory

Dataclass описывает поля экземпляров, поэтому фабрика должна создавать коллекцию для каждого экземпляра, которому нужно значение по умолчанию. Передавайте сам list, а не list(): второй вариант сразу вызовет конструктор и передаст список вместо вызываемого объекта.

Пример печатает ['SQL'] [], затем False. Изменение first.topics не затрагивает second.topics. Современный dataclasses отклоняет многие изменяемые значения полей по умолчанию; используйте документированный механизм фабрики, а не рассчитывайте на детали проверки.

from dataclasses import dataclass, field

@dataclass
class ReviewSession:
    topics: list[str] = field(default_factory=list)

first = ReviewSession()
second = ReviewSession()
first.topics.append("SQL")
print(first.topics, second.topics)
print(first.topics is second.topics)

Проверяйте идентичность вместе с содержимым

Тест с единственным вызовом remember пройдёт и для сломанной функции. Сделайте два вызова без аргумента, проверьте независимое содержимое, затем измените один результат и убедитесь, что второй не поменялся. Отдельный тест с явно переданным пустым списком защитит выбранный контракт от замены is None на проверку истинности.

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

Коротко

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

Проблема касается словарей и множеств?

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

Почему is None, а не == None?

Контракт требует проверки идентичности с единственным объектом None. Равенство может быть переопределено типом аргумента и для обнаружения такого маркера не нужно.

Достаточно ли копировать список по умолчанию в начале?

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

Источники

Источники и редакционная политика

Сведения проверены 21 октября 2026 г. Ссылки рядом с разделами указывают источники фактов и технических объяснений. Выводы, учебные сценарии и рекомендации по подготовке — редакционная работа RecallDeck.

От чтения к воспроизведению

Отрепетируйте полный цикл интервью.

RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.

Начать подготовку

Продолжить подготовку