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 в качестве значения по умолчанию и создавать новый объект внутри функции, если это необходимо.