У Django, враховуючи те, QuerySet
що я маю ітерацію та друк результатів, який найкращий варіант для підрахунку об’єктів? len(qs)
чи qs.count()
?
(Також враховуючи, що підрахунок об’єктів в одній ітерації не є можливим.)
У Django, враховуючи те, QuerySet
що я маю ітерацію та друк результатів, який найкращий варіант для підрахунку об’єктів? len(qs)
чи qs.count()
?
(Також враховуючи, що підрахунок об’єктів в одній ітерації не є можливим.)
Відповіді:
Хоча документи Django рекомендують використовувати, count
а не len
:
Примітка: Не використовуйте
len()
QuerySets, якщо все, що ви хочете зробити, це визначити кількість записів у наборі. Набагато ефективніше обробляти підрахунок на рівні бази даних, використовуючи SQLSELECT COUNT(*)
, і Django пропонуєcount()
метод саме з цієї причини.
Оскільки ви в будь-якому випадку повторюєте цей QuerySet, результат буде кешовано (якщо ви не використовуєте iterator
), і тому його буде переважно використовувати len
, оскільки це дозволяє уникнути повторного потрапляння в базу даних, а також можливо отримання різної кількості результатів !) .
Якщо ви використовуєте iterator
, я б запропонував включати змінну підрахунку під час ітерації (а не за допомогою count) з тих самих причин.
Вибір між ситуацією len()
і count()
залежить від ситуації, і варто глибоко зрозуміти, як вони працюють, щоб правильно їх використовувати.
Дозвольте мені запропонувати вам кілька сценаріїв:
(найважливіше) Коли ви хочете знати лише кількість елементів, і ви не плануєте їх будь-яким способом обробляти, важливо використовувати count()
:
DO: queryset.count()
- це буде виконувати один SELECT COUNT(*) some_table
запит, усі обчислення виконуються на стороні RDBMS, 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()
вдарить db за допомогою 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()
майже вдвічі швидше, ніж len()
у цьому конкретному тестовому випадку.
Підсумовуючи те, що вже відповіли інші:
len()
отримає всі записи та перегляне їх.count()
буде виконувати операцію SQL COUNT (набагато швидше при роботі з великим набором запитів).Також вірно, що якщо після цієї операції буде повторено весь набір запитів, то в цілому він може бути трохи ефективнішим у використанні len()
.
Однак
У деяких випадках, наприклад, при обмеженнях пам'яті, може бути зручно (коли це можливо) розділити операцію, виконану на записи. Цього можна досягти за допомогою пагінації django .
Тоді використання count()
буде вибором, і ви зможете уникнути необхідності отримувати весь набір запитів одночасно.