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ę).
python
django
performance
antonagestam
źródło
źródło
Odpowiedzi:
Chociaż dokumentacja Django zaleca używanie
count
zamiastlen
: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.źródło
Wybór między
len()
icount()
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 pojedynczeSELECT 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 wykonaSELECT * 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 jakcount()
: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:
źródło
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 danychqs.count()
zselect count(*) from table
.również
qs.count()
zwróci liczbę całkowitą i nie możesz po niej iterowaćźródło
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.źródło
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.źródło