Значения по умолчанию в python

Nov 1, 2014 16:55 · 277 words · 2 minute read python

Проходил как-то собеседование на должность python-разработчика, одним из вопросов был что выведет следующий код:

def f(value=[]):
    value.append(1)
    print value

f()
f([1])
f()

К сожалению, хоть я и знал в чём подвох (значения по умолчанию в python не так уж просты :) ), но ответил неправильно. Что же тут не так можно посмотреть под катом.

Всё дело тут в т.н. значении по умолчанию, которое на самом деле не совсем уж значение, а очень даже переменная. Фишка в том, что при разборе кода компилятор вычисляет значение (в данном случае []) и записывает в value, которая определена в области видимости функции f. В дельнейшем, если оная будет вызываться с параметром, то в value будет использоваться параметр, в противном случае - значение по умолчанию, которое получили ранее. Но! Оно меняется… Сам в шоке :) а всё потому, что константным остаётся ссылка, т.е. между вызовами f value сохраняет своё состояние. Убедиться в этом можно на примере пошаговово выполнения:

Итак, проблема ясна, а как же выкручиваться? Первое, что приходит в голову - не трогать значение по умолчанию и сохранять его в какую-нибудь другую переменную:

Упс, не помогло :( Видимо, придётся каждый раз полностью копировать исходное значение:

Что там Гвидо говорил: “Явное лучше неявного”? :) Ну-ну…

UPD

И вот снова наступил на эти грабли уже в Django. Была задача сделать выпадающий список с элементами из базы данных. Естественно, первое, что пришло в голову:

some_field = forms.ChoiceField(choices=get_choices())

Но вот выполнение get_choices происходит только один раз - при запуске приложения. К счастью, я не одинок в своей проблеме, в интернете нашлось “решение” (скорее костыль) через инициализацию этого поля в конструкторе:

class MyCustomForm(forms.Form):
   other_field = forms.CharField()
...
   def __init__(self, *args, **kwargs):
       super(MyCustomForm, self).__init__(*args, **kwargs)
       self.fields["some_field"] = forms.ChoiceField(choices=get_choices())