В Django, учитывая, что у меня есть QuerySet
который я собираюсь повторить и распечатать результаты, каков наилучший вариант для подсчета объектов? len(qs)
или qs.count()
?
(Также учитывая, что подсчет объектов в одной итерации не вариант.)
В Django, учитывая, что у меня есть QuerySet
который я собираюсь повторить и распечатать результаты, каков наилучший вариант для подсчета объектов? len(qs)
или qs.count()
?
(Также учитывая, что подсчет объектов в одной итерации не вариант.)
Хотя Django Docs рекомендует использовать count
а не len
:
Примечание. Не используйте
len()
в QuerySets, если все, что вам нужно, это определить количество записей в наборе. Намного эффективнее обрабатывать счет на уровне базы данных, используя SQLSELECT COUNT(*)
, и Django предоставляет методcount()
именно по этой причине.
Так как вы все равно выполняете итерацию этого QuerySet, результат будет кэшироваться (если вы не используете iterator
), и поэтому будет предпочтительно использовать len
, так как это позволит избежать повторного попадания в базу данных, а также, возможно, получения другого количества результатов. !).
Если вы используете iterator
, то я бы предложил включить подсчетную переменную во время итерации (а не использования счетчика) по тем же причинам.
Выбор между len()
и count()
зависит от ситуации, и стоит глубоко понять, как они работают, чтобы правильно их использовать.
Позвольте мне представить вам несколько сценариев:
(наиболее важно) Если вы хотите знать только количество элементов и не планируете обрабатывать их каким-либо образом, крайне важно использовать count()
:
DO: queryset.count()
- это будет выполнять один запрос SELECT COUNT(*) some_table
, все вычисления выполняются на стороне СУБД, Python просто нужно получить номер результата с фиксированной стоимостью O (1)
DON'T: len(queryset)
- это выполнит запрос SELECT * FROM some_table
, извлекая всю таблицу O (N) и требуя дополнительной памяти O (N) для ее хранения. Это худшее, что можно сделать
Когда вы намереваетесь в любом случае извлечь набор запросов, лучше использовать len()
который не вызовет дополнительный запрос к базе данных, поскольку count()
будет:
len(queryset) # fetching all the data - NO extra cost - data would be fetched anyway in the for loop
for obj in queryset: # data is already fetched by len() - using cache
pass
Количество:
queryset.count() # this will perform an extra db query - len() did not
for obj in queryset: # fetching data
pass
Вернулся второй случай (когда набор запросов уже был получен):
for obj in queryset: # iteration fetches the data
len(queryset) # using already cached data - O(1) no extra cost
queryset.count() # using cache - O(1) no extra db query
len(queryset) # the same O(1)
queryset.count() # the same: no query, O(1)
Все будет ясно, как только вы загляните "под капот":
class QuerySet(object):
def __init__(self, model=None, query=None, using=None, hints=None):
# (...)
self._result_cache = None
def __len__(self):
self._fetch_all()
return len(self._result_cache)
def _fetch_all(self):
if self._result_cache is None:
self._result_cache = list(self.iterator())
if self._prefetch_related_lookups and not self._prefetch_done:
self._prefetch_related_objects()
def count(self):
if self._result_cache is not None:
return len(self._result_cache)
return self.query.get_count(using=self.db)
Хорошие ссылки в Django документах:
Я думаю, что использование len(qs)
имеет больше смысла здесь, поскольку вам нужно перебирать результаты. qs.count()
- лучший вариант, если все, что вы хотите сделать, распечатывает счетчик и не перебирает результаты.
len(qs)
попадет в базу данных с помощью select * from table
, тогда как qs.count()
ударит по db с помощью select count(*) from table
.
также qs.count()
даст целое число return и вы не можете его перебирать