Mutable Default Arguments

Если вы используете изменяемые (mutable) аргументы вместе с аргументами по умолчанию (default arguments) в Python, это может привести к неожиданным результатам.
Давайте покажем это на простом примере. Представьте, что вы преподаватель и хотите написать функцию для записи оценок студентов за разные задания. Вы хотите использовать список для хранения этих оценок. Тогда изначально вы можете написать что-то в таком духе:
def record_score(new_score, score_list=[]):
    score_list.append(new_score)
    return score_list

print(record_score(95))  # [95]
print(record_score(85))  # [95, 85]
Кажется, что все работает правильно. Функция record_score принимает оценку и список. Если список не передан, используется пустой список (это следует из записи score_list=[] в определении функции).
Когда вы вызываете record_score(95), функция действительно возвращает список с оценкой 95. Но при последующем вызове record_score(85) результат оказывается списком, где есть и 95, и 85 — будто функция «помнит» старую версию списка.
Это может показаться странным, ведь во многих языках программирования при завершении работы функции все ее локальные переменные (например, score_list) теряются, и при новом вызове всё начинается с нуля.
Однако в Python при использовании изменяемого объекта в качестве аргумента по умолчанию ситуация иная. Python создает такой аргумент только один раз — когда функция определяется. Поэтому при каждом вызове record_score без передачи собственного списка используется один и тот же список, созданный при определении. Если вы изменяете этот список (например, добавляете в него новую оценку), изменение сохраняется и для следующего вызова.
В нашей функции score_list получается общим для всех вызовов, где мы не передаем свой список. Поэтому оценки незаметно накапливаются.
Но это явно не то, что нам нужно! Мы хотели, чтобы каждый вызов record_score начинал работу с пустым списком, если свой список не передан.
Чтобы этого избежать, обычно используют None вместо изменяемого объекта в значении по умолчанию, а внутри функции создают новый объект только если аргумент оказался равен None. Вот как можно переписать функцию record_score, чтобы избавиться от проблемы:
def record_score(new_score, score_list=None):
    if score_list is None:  # Если список не задан,
        score_list = []     # создаем новый
    score_list.append(new_score)
    return score_list

print(record_score(95))  # [95]
print(record_score(85))  # [85]
В этой исправленной версии, при каждом вызове record_score без собственного списка создается новый пустой список, и оценки больше не переносятся из одного вызова в другой.
💡
Поэтому главный вывод таков: Изменяемые аргументы по умолчанию в Python могут вести к неожиданному поведению, поскольку создаются всего один раз, и один и тот же объект используется при каждом вызове функции. Это может быть нежелательно, особенно при изменении этого объекта. Чтобы избежать такой ситуации, можно использовать None в качестве значения по умолчанию и создавать новый объект внутри функции, если это необходимо.
 
To check your solution you need to sign in
Sign in to continue