21

Django的bug分析记录 - got multiple values for argument 'negate' - greenblog

 3 years ago
source link: https://www.greenblog.cn/15?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

got multiple values for argument 'negate'

Django

平时我都会去关注一些开源项目的issue处理情况,目的是主要是学习优秀的编程思维和处理方式,也能观察出这个项目的活跃情况。但很多问题看过就忘,所以我将持续的写一个bug分析的系列,挑选开源项目中个人认为比较有特色的bug,解析其中的问题和想法并举一反三。

这是一个Django的bug:#31783 Filtering on a field named negate raises a TypeError

根据issue描述,可以简单复现:

from django.db.models.query import QuerySet
QuerySet().filter(negate=False)

执行就会报错:

File "/Volumes/data/work/DjangoTicket/Test31783/venv/lib/python3.7/site-packages/django/db/models/query.py", line 904, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
TypeError: _filter_or_exclude() got multiple values for argument 'negate'

从报错信息就可以分析出是_filter_or_exclude这个函数的参数冲突了,查看django源码:

# django/db/models/query.py
def filter(self, *args, **kwargs):
    """
    Return a new QuerySet instance with the args ANDed to the existing
    set.
    """
    self._not_support_combined_queries('filter')
    return self._filter_or_exclude(False, *args, **kwargs)
 
 ...
 
 def _filter_or_exclude(self, negate, *args, **kwargs):
    if args or kwargs:
        assert not self.query.is_sliced, \
            "Cannot filter a query once a slice has been taken."

    clone = self._chain()
    if negate:
        clone.query.add_q(~Q(*args, **kwargs))
    else:
        clone.query.add_q(Q(*args, **kwargs))
    return clone

可以看到filter函数会调用_filter_or_exclude并传入negate参数为False,这样就会导致我们传入的negate=False参数冲突。 查看git提交历史该bug应该非常早就存在了,这也是Python开发者比较容易犯的错误。

issue里的解决方案是修改_filter_or_exclude(self, negate, *args, **kwargs)函数为_filter_or_exclude(self, negate, args, kwargs)

是否可以换个角度去解决,可以将_filter_or_exclude函数拆分成_filter_exclude两个分工不同的函数,这样就不需要negate参数了,自然解决的了这个bug

这个错误在Python的实现中是什么位置抛出的?Python 是如何校验参数的? 要解答这些疑问就要深入CPython源码了,先来查找got multiple values for argument这个错误代码是在什么位置,全局查找后发现是在CPython/Python/ceval.c文件中:

  kw_found:
    if (GETLOCAL(j) != NULL) {
        _PyErr_Format(tstate, PyExc_TypeError,
                      "%U() got multiple values for argument '%S'",
                      qualname, keyword);
        goto fail;
    }
    Py_INCREF(value);
    SETLOCAL(j, value);

这个错误是kw_found的处理代码抛出的,那么什么情况会goto kw_found呢?向上排查发现:

    /* Handle keyword arguments passed as two strided arrays */
    kwcount *= kwstep;
    for (i = 0; i < kwcount; i += kwstep) {
        ...

        /* Speed hack: do raw pointer compares. As names are
           normally interned this should almost always hit. */
        co_varnames = ((PyTupleObject *)(co->co_varnames))->ob_item;
        for (j = co->co_posonlyargcount; j < total_args; j++) {
            PyObject *varname = co_varnames[j];
            if (varname == keyword) {
                goto kw_found;
            }
        }

        /* Slow fallback, just in case */
        for (j = co->co_posonlyargcount; j < total_args; j++) {
            PyObject *varname = co_varnames[j];
            int cmp = PyObject_RichCompareBool( keyword, varname, Py_EQ);
            if (cmp > 0) {
                goto kw_found;
            }
            else if (cmp < 0) {
                goto fail;
            }
        }

从代码变量中就可以明白这段代码主要的作用就是遍历kwargs,比对kwargs中的key名称是否和函数参数中的名称相同。代码中的注释也说明了cpython做的对比优化。

本文由 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2020/07/24 09:19


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK