Почему изменяемые типы не рекомендуется использовать в качестве значений аргументов по умолчанию в Python? Если вы забрались по карьерной лестнице выше джуниора, то наверняка задумывались. И наверняка ответ был таким: «Это приводит к странным побочным эффектам». Но я почти наверняка уверен, что только малая часть разработчиков на Python сделала шаг дальше и разобралась, почему такое поведение у языка.
Давайте разберёмся сначала с тем, с какими побочными эффектами имеем дело. Напишем вот такую функцию:
def foo(param=[]):
print(param)
>>> foo()
[]
Сколько её ни вызывай, значение, выводимое на экран, не изменится. Никаких побочных эффектов. Но если её модифицировать вот так:
def foo(param=[]):
param.append(1)
print(param)
То обнаружим, что значение на экране будет меняться в зависимости от того, сколько раз вызвана функция:
>>> foo()
[1]
>>> foo()
[1, 1]
>>> foo()
[1, 1, 1]
Но мы ведь хотели не этого. Такое поведение функций — один из лидеров разного рода списков «Х самых странных вещей в Python». Хотя, если разобраться с внутренней «кухней» языка, то всё встанет на свои места и «странность» превратится в логичное поведение.
Почему же так получилось? Всё из‑за того, что функция — это объект, как и все другие сущности в Python. При создании объекта дефолтные значения параметров упаковываются в кортеж foo.func_defaults.
Списки в Python — тип изменяемый. А объект функции создаётся один раз. Так что объект, на который ссылается foo.func_defaults[0], и param внутри функции — один и тот же:
def foo(param=[]):
print(id(param))
>>> foo()
4494207600
>>> id(foo.func_defaults[0])
4494207600
Правильным будет передача None в качестве значения по умолчанию. А в теле функции уже инициализировать изменяемый объект. А подсказку по типу параметра прописывать в докстринге или, если используется Python 3.5+, то использовать PEP 0484:
def foo(param=None):
"""
:type param: list
"""
param = [] if param is None else param
param.append(1)
print(param)
>>> foo()
[1]
>>> foo()
[1]
Для коротких функций в 5–6 строк добавление проверки на None может показаться «замусориванием» кода. В этих случаях нужно хорошо подумать: оставить эту проверку и обезопасить код или убрать её. Я за безопасность: потом, делая рефакторинг, можно пропустить изменения, которые внесут побочные эффекты.