В Django, учитывая, что у меня есть объект, QuerySet
который я собираюсь перебрать и распечатать результаты, каков наилучший вариант для подсчета объектов? len(qs)
или qs.count()
?
(Также учитывая, что подсчет объектов в одной итерации не является вариантом.)
python
django
performance
антонагестам
источник
источник
Ответы:
Хотя в документации Django рекомендуется использовать,
count
а неlen
:Поскольку вы все равно повторяете этот QuerySet, результат будет кэширован (если вы не используете
iterator
), и поэтому его будет предпочтительнее использоватьlen
, поскольку это позволяет избежать повторного попадания в базу данных, а также, возможно, получить другое количество результатов !) .Если вы используете
iterator
, я бы предложил включить счетную переменную при повторении (вместо использования счетчика) по тем же причинам.источник
Выбор между
len()
иcount()
зависит от ситуации, и стоит глубоко понять, как они работают, чтобы использовать их правильно.Позвольте предложить вам несколько сценариев:
(наиболее важно) Если вы хотите знать только количество элементов и не планируете их обрабатывать каким-либо образом, важно использовать
count()
:DO:
queryset.count()
- это будет выполнять одинSELECT COUNT(*) some_table
запрос, все вычисления выполняются на стороне СУБД, Python просто нужно получить номер результата с фиксированной стоимостью O (1)НЕ:
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
Отмененный 2-й случай (когда набор запросов уже был получен):
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:
источник
QuerySet
реализации в контексте.Я думаю, что использование здесь
len(qs)
имеет больше смысла, поскольку вам нужно перебирать результаты.qs.count()
- лучший вариант, если все, что вы хотите сделать, это распечатать счетчик, а не перебирать результаты.len(qs)
попадет в базу данных с,select * from table
тогда какqs.count()
попадет в БД сselect count(*) from table
.также
qs.count()
вернет целое число, и вы не можете перебирать егоисточник
Для людей, предпочитающих тестовые измерения (Postresql):
Если у нас есть простая модель Person и 1000 ее экземпляров:
class Person(models.Model): name = models.CharField(max_length=100) age = models.SmallIntegerField() def __str__(self): return self.name
В среднем это дает:
In [1]: persons = Person.objects.all() In [2]: %timeit len(persons) 325 ns ± 3.09 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) In [3]: %timeit persons.count() 170 ns ± 0.572 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Итак, как вы можете видеть
count()
почти в 2 раза быстрее, чемlen()
в этом конкретном тестовом примере.источник
Обобщая то, что уже ответили другие:
len()
будет извлекать все записи и перебирать их.count()
выполнит операцию SQL COUNT (намного быстрее при работе с большим набором запросов).Верно также и то, что если после этой операции будет повторен весь набор запросов, то в целом его можно будет использовать несколько эффективнее
len()
.Однако
В некоторых случаях, например, при наличии ограничений памяти, может быть удобно (когда возможно) разделить выполняемую операцию по записям. Этого можно добиться с помощью разбивки на страницы django .
Тогда использование
count()
было бы лучшим выбором, и вы могли бы избежать одновременной выборки всего набора запросов.источник