W Django, biorąc pod uwagę, QuerySet
że mam zamiar powtórzyć i wydrukować wyniki, jaka jest najlepsza opcja do liczenia obiektów? len(qs)
czy qs.count()
?
(Również biorąc pod uwagę, że liczenie obiektów w tej samej iteracji nie wchodzi w grę).
W Django, biorąc pod uwagę, QuerySet
że mam zamiar powtórzyć i wydrukować wyniki, jaka jest najlepsza opcja do liczenia obiektów? len(qs)
czy qs.count()
?
(Również biorąc pod uwagę, że liczenie obiektów w tej samej iteracji nie wchodzi w grę).
Odpowiedzi:
Chociaż dokumentacja Django zaleca używanie count
zamiast len
:
Uwaga: nie używaj
len()
w QuerySets, jeśli chcesz tylko określić liczbę rekordów w zestawie. O wiele wydajniej jest obsłużyć liczenie na poziomie bazy danych, używając SQLSELECT COUNT(*)
, a Django zapewniacount()
metodę właśnie z tego powodu.
Ponieważ i tak wykonujesz iterację tego zestawu QuerySet, wynik zostanie zapisany w pamięci podręcznej (chyba że używasz iterator
), więc lepiej będzie go użyć len
, ponieważ pozwala to uniknąć ponownego uderzenia w bazę danych, a także ewentualnego pobrania innej liczby wyników !) .
Jeśli używasz iterator
, sugerowałbym włączenie zmiennej liczącej podczas iteracji (zamiast używania count) z tych samych powodów.
Wybór między len()
i count()
zależy od sytuacji i warto dogłębnie zrozumieć, jak działają, aby prawidłowo z nich korzystać.
Pozwólcie, że przedstawię kilka scenariuszy:
(najważniejsze) Jeśli chcesz znać tylko liczbę elementów i nie planujesz ich w żaden sposób przetwarzać, kluczowe jest użycie count()
:
ZRÓB: queryset.count()
- to wykona pojedyncze SELECT COUNT(*) some_table
zapytanie, wszystkie obliczenia są wykonywane po stronie RDBMS, Python musi tylko pobrać numer wyniku ze stałym kosztem O (1)
NIE: len(queryset)
- to wykona SELECT * FROM some_table
zapytanie, pobierając całą tabelę O (N) i wymagając dodatkowej pamięci O (N) do jej przechowywania. To najgorsze, co można zrobić
Jeśli mimo wszystko zamierzasz pobrać zestaw zapytań, lepiej jest użyć go, len()
który nie spowoduje dodatkowego zapytania do bazy danych, tak jak 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
Liczyć:
queryset.count() # this will perform an extra db query - len() did not
for obj in queryset: # fetching data
pass
Cofnięto drugi przypadek (gdy zestaw zapytań został już pobrany):
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)
Wszystko będzie jasne, gdy spojrzysz „pod maskę”:
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)
Dobre odniesienia w dokumentacji Django:
QuerySet
implementacji w kontekście.
Myślę, że używanie len(qs)
ma tutaj więcej sensu, ponieważ musisz iterować wyniki. qs.count()
jest lepszą opcją, jeśli wszystko, co chcesz zrobić, drukuje licznik i nie iteruje wyników.
len(qs)
trafi do bazy danych z, select * from table
a do bazy danych qs.count()
z select count(*) from table
.
również qs.count()
zwróci liczbę całkowitą i nie możesz po niej iterować
Dla osób preferujących pomiary testowe (Postresql):
Jeśli mamy prosty model Person i 1000 jego instancji:
class Person(models.Model):
name = models.CharField(max_length=100)
age = models.SmallIntegerField()
def __str__(self):
return self.name
W przeciętnym przypadku daje:
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)
Jak więc możesz widzieć count()
prawie 2x szybciej niż len()
w tym konkretnym przypadku testowym.
Podsumowując, na co inni już odpowiedzieli:
len()
pobierze wszystkie rekordy i iteruje po nich.count()
wykona operację SQL COUNT (znacznie szybciej w przypadku dużego zestawu zapytań).Prawdą jest również, że jeśli po tej operacji cały zestaw zapytań będzie iterowany, to jako całość może być nieco bardziej efektywny w użyciu len()
.
jednak
W niektórych przypadkach, na przykład w przypadku ograniczeń pamięci, może być wygodne (o ile to możliwe) podzielenie wykonywanej operacji na rekordy. Można to osiągnąć za pomocą paginacji django .
Wtedy użycie count()
byłoby wyborem i można by uniknąć konieczności pobierania całego zestawu zapytań na raz.