Почему queryset [0] и queryset.first () возвращают разные записи? - PullRequest
2 голосов
/ 05 марта 2020

Сегодня я обнаружил, что могу получать доступ к элементам в наборе запросов, ссылаясь на них с помощью индекса, то есть queryset[n]. Однако сразу после того, как я обнаружил, что queryset[0] не возвращает ту же запись, что и queryset.first(). Почему это и является одним из тех, кто более "правильный"? (Я знаю, что .first() быстрее, но кроме этого)

Python 3.7.4
django 1.11.20

1 Ответ

2 голосов
/ 06 марта 2020

Существует небольшая семантическая разница между qs[0] и qs.first(). Если вы не указали порядок в наборе запросов самостоятельно, то Django упорядочит сам набор запросов по первичному ключу, прежде чем извлекать первый элемент.

Кроме того, .first() вернет None если набор запросов пуст . Принимая во внимание, что qs[0] повысит IndexError.

Заявление о том, что .first() быстрее, однако составляет , а не True. Фактически, если вы используете qs[<i>n</i>], то Django будет, за кулисами, извлекать запись путем нарезки с помощью qs[<i>n</i>:<i>n</i>+1], следовательно, с учетом того, что серверная часть базы данных поддерживает это, сделает запрос с LIMIT 1 OFFSET <i>n</i> и, таким образом, получит одна запись, как и в случае .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 <b>objects[0]</b>
        return None

Как видите, если набор запросов уже упорядочен (self.ordered равен True, тогда мы берем self[:1], проверяем, есть ли запись, и если это так, верните его. Если нет, мы вернем None.

Код для извлечения элемента по указанному индексу c является более криптовым c. По сути, он устанавливает пределы от k до k+1, материализуйте элемент и верните первый элемент, как мы видим в исходном коде [GitHub] :

    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 <b>list(qs)[0]</b>
...