python/django方法中由于”默认参数值可变”而导致的随机数据库查询结果

Python/Django random DB query results due to “Default argument value is mutable” in method

昨天我在我的django应用程序中遇到了一个bug,尽管我修复了它,但我仍然不理解它的原因,也不知道如何解决它。

实际上,我在写这个问题的时候找到了根本原因,这要归功于"标题相似的问题"功能。参见"最小惊异"和可变默认参数

但是,我仍然不明白这会以什么方式影响我的应用程序。所以,让我们深入研究。

我有一个网页,它显示一个项目列表。我通过views.py文件中的Model.get方法查询这些项目。没有什么不寻常的,基本上从模型中获取数据库,从视图中调用模型,并向模板提供一个带有获取值的变量。

当使用错误的源代码时,我将刷新页面,这些项将随机出现或消失。我认为数据库查询要么返回项目,要么返回空列表。

以下是错误的源代码(model.py):

1
2
3
4
5
6
7
8
  @classmethod
  def get(cls, school=None, additional_filters={}):
    if school:
      additional_filters['school'] = school

    return MyModel.objects.filter(
      **additional_filters
    )

以下是我修复它的方法:

1
2
3
4
5
6
7
8
9
10
11
  @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有问题,而且因为我根本无法解释这个错误,所以我遵循了它的建议。

但我还是不明白为什么。即使现在,在阅读了"最不惊讶"和可变的默认参数之后,我仍然没有。

现在我明白了,由于Python处理内存中默认函数参数的方式,每次调用时都会修改additional_filters

我不能解释的是这种行为的副作用。为什么查询返回了正确的项或空集?特别是考虑到代码没有提供任何additional_filters,这意味着additional_filters中唯一添加的项目是school,在每个查询中都是相同的。

我真的不明白这一点。我对这个方法的所有调用都是Model.get(request.context.school)的形式,并且由于additional_filters是一个映射而不是数组,所以它应该始终包含相同的值。

这个bug花了我一段时间才弄清楚,因为我不能在本地环境或登台环境中复制它,它只影响生产环境,使它很难定位。


fcgi维护一个进程池。每个池成员都有一个空dict作为默认关键字参数。您最初很幸运,请求通过未更改的空dict到达进程。但是每当一个请求到达已经修改了字典的进程时,它就不再是一个空的dict了——您的过滤器会累积起来。