为什么 queryset[0] 和 queryset.first() 返回不同的记录?

Sai*_*cko 5 python django django-queryset

今天我发现我可以通过使用索引引用查询集中的元素来访问它们,即queryset[n]. 然而,在我发现它并queryset[0]没有返回与 相同的记录之后queryset.first()。为什么会这样,是其中之一更“正确”吗?(我知道这样.first()更快,但除此之外)

Python 3.7.4
Django 1.11.20

Wil*_*sem 6

qs[0]和之间存在细微的语义差异qs.first()。如果您自己没有在查询集中指定顺序,那么 Django 将在获取第一个元素之前按主键对查询集本身进行排序。

此外,如果查询集为空,.first()则返回。而将提出.Noneqs[0]IndexError

.first()然而更快的说法却并非如此 True。事实上,如果你使用,那么 Django 将在幕后通过 切片 来获取记录,因此,如果数据库后端支持这一点,它会使用 进行查询,从而获取一条记录,就像会做的那样。如果已经检索到查询集,则它根本不会进行任何额外的查询因为数据已经被缓存。qs[n]qs[n:n+1]LIMIT 1 OFFSET n.first()

您可以在GitHub上查看实现:

    def first(self):
        """
        Returns the first object of a query, returns None if no match is found.
        """
        objects = list((self if self.ordered else self.order_by('pk'))[:1])
        if objects:
            return objects[0]
        return None
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,如果查询集已经有序(self.orderedis True,那么我们采取self[:1],检查是否有记录,如果有则返回它。如果没有,我们返回None

用于检索特定索引处的项目的代码更加神秘。k它本质上会设置从到 的限制,具体化该项目,并返回第一个项目,正如我们在源代码 [GitHub]k+1中看到的:

    def __getitem__(self, k):
        """
        Retrieves an item or slice from the set of results.
        """
        if not isinstance(k, (slice,) + six.integer_types):
            raise TypeError
        assert ((not isinstance(k, slice) and (k >= 0)) or
                (isinstance(k, slice) and (k.start is None or k.start >= 0) and
                 (k.stop is None or k.stop >= 0))), \
            "Negative indexing is not supported."

        if self._result_cache is not None:
            return self._result_cache[k]

        if isinstance(k, slice):
            qs = self._clone()
            if k.start is not None:
                start = int(k.start)
            else:
                start = None
            if k.stop is not None:
                stop = int(k.stop)
            else:
                stop = None
            qs.query.set_limits(start, stop)
            return list(qs)[::k.step] if k.step else qs

        qs = self._clone()
        qs.query.set_limits(k, k + 1)
        return list(qs)[0]
Run Code Online (Sandbox Code Playgroud)