Вчера я столкнулся с ошибкой в своем приложении Django, и хотя с тех пор исправил ее, я до сих пор не понимаю ее причину и то, как я ее исправил.
Ну, на самом деле я нашел основную причину, когда писал этот вопрос, благодаря функции SO "Вопросы с похожими заголовками". См. «Наименьшее изумление» и изменчивый аргумент по умолчанию
Buuut, я до сих пор не понимаю, как это могло повлиять на мое приложение, как оно повлияло на него. Итак, давайте копаться.
У меня есть веб-страница, на которой отображается список элементов. Я запрашиваю эти элементы с помощью метода Model.get
из файла views.py. Ничего необычного, в основном выборка БД из модели, вызов модели из представления и предоставление переменной с извлеченными значениями в шаблон.
При использовании неверного исходного кода я обновлял страницу, и элементы случайным образом либо появлялись, либо исчезали. Я полагал, что запрос БД либо вернет элементы, либо вернет пустой список.
Вот неверный исходный код (model.py):
@classmethod
def get(cls, school=None, additional_filters={}):
if school:
additional_filters['school'] = school
return MyModel.objects.filter(
**additional_filters
)
А вот как я это исправил:
@classmethod
def get(cls, school=None, additional_filters=None):
if not additional_filters:
additional_filters = {}
if school:
additional_filters['school'] = school
return MyModel.objects.filter(
**additional_filters
)
Я исправил это таким образом, потому что PyCharm IDE сказал мне, что что-то не так Default argument value is mutable
и, поскольку я вообще не мог объяснить ошибку, я последовал ее рекомендациям.
Но я до сих пор не понимаю, почему. И даже сейчас, после прочтения «Наименьшее удивление» и изменяемого аргумента по умолчанию я все еще не знаю.
Теперь я понимаю, что additional_filters
изменялись при каждом вызове из-за того, как Python обрабатывает аргументы функций по умолчанию в памяти.
Что я не объясняю, так это побочный эффект этого поведения. Почему запрос вернул либо правильные элементы, либо пустой набор? Особенно учитывая, что код не предоставил additional_filters
, то есть единственный добавленный элемент в additional_filters
был school
, который всегда был одинаковым при каждом запросе.
Эту часть я действительно не понимаю. Все мои вызовы этого метода имели вид Model.get(request.context.school)
, и поскольку additional_filters
является картой, а не массивом, в нем всегда должно содержаться одно и то же значение.
Эта ошибка заняла у меня некоторое время, чтобы выяснить, потому что я не мог воспроизвести ее ни в своей локальной среде, ни в промежуточной среде, она затрагивала только производственную среду и очень сильно затрудняла ее обнаружение.