Для набора запросов из одного элемента индексация 0-го элемента a (query_set[0]) и query_set.first() дают разные значения - Django 2.2.7

Я выполняю следующий код:

        city_state_location = (
            Location.objects.filter(city=city, state=state)
            .values_list("city")
            .annotate(long=Avg("longitude"), lat=Avg("latitude"))
        )
        print(
            city_state_location,
            city_state_location[0],
            city_state_location.first(),
            len(city_state_location),
        )

Что выводит:

<QuerySet [('New York', -73.9422666666667, 40.8382)]>
('New York', -73.9422666666667, 40.8382)
('New York', -73.9501, 40.8252)
1

Почему это происходит? Набор запросов содержит только один элемент, поэтому я не понимаю, почему индексирование первого элемента отличается от вызова .first()

Это работает на Django 2.2.7

Почему это происходит?

Вопреки распространенному мнению, .first() и [0] отличаются. Действительно, .first() добавит оговорку .order_by('pk') на случай, если вы неправильно упорядочите кверисет, в результате чего не будет GROUP BY city, так как первичный ключ является частью процесса упорядочивания, поэтому первый элемент - это просто первый Location объект, упорядоченный по первичному ключу, который удовлетворяет заданной фильтрации, а также Avg('longitude') и Avg('latitude'). Действительно, если мы посмотрим на исходный код .first() [GitHub], то увидим:

def first(self):
    """Return the first object of a query or None if no match is found."""
    for obj in (self if self.ordered else self.order_by('pk'))[:1]:
        return obj

Таким образом, будет возвращена долгота и широта для первого элемента.

С другой стороны, использование [0] не добавит дополнительный .order_by('pk'), поэтому возвращается элемент первой группы элементов, удовлетворяющих условию фильтра, который, таким образом, определяет среднее значение всех элементов, указывающих на 'New York' в данном случае.

Если бы вы использовали city_state_location.order_by('pk')[0], то произошло бы то же самое, что и при использовании .first().

Вернуться на верх