Изменяемы типы в качестве параметров по умолчанию в Python
Опубликовано 11 April 2016 в 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". Хотя, если разобраться с внутренней кухней языка, то все встанет на свои места и "странность" превратится в логичное поведение.
Почему же так получилось? Все из-за того, что функция - это объект, как и все другие сущности в питоне. При создании объекта дефолтные значения параметров упаковались в tuple foo.func_defaults.
Списки в питоне - тип изменяемый. А объект функции создается один раз. Так что объект на который ссылается 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 - замусоривание кода. В этих случаях нужно хорошо подумать оставить эту проверку и обезопасить код или убрать ее. Я за безопасность. Потом делая рефакторинг можно пропустить изменения, которые внесут побочные эффекты.
Возник вопрос? Мне всегда можно написать в Twitter: avkorablev