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 countzamiast 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_tablezapytanie, 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_tablezapytanie, 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:
QuerySetimplementacji 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 tablea 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.