In Django, dato che ho un file su QuerySet
cui iterare e stampare i risultati, qual è l'opzione migliore per contare gli oggetti? len(qs)
o qs.count()
?
(Anche dato che contare gli oggetti nella stessa iterazione non è un'opzione.)
In Django, dato che ho un file su QuerySet
cui iterare e stampare i risultati, qual è l'opzione migliore per contare gli oggetti? len(qs)
o qs.count()
?
(Anche dato che contare gli oggetti nella stessa iterazione non è un'opzione.)
Risposte:
Sebbene i documenti di Django consigliano di utilizzare count
anziché len
:
Nota: non utilizzare
len()
su QuerySet se tutto quello che vuoi fare è determinare il numero di record nel set. È molto più efficiente gestire un conteggio a livello di database, utilizzando SQLSELECT COUNT(*)
, e Django fornisce uncount()
metodo proprio per questo motivo.
Dato che stai comunque iterando questo QuerySet, il risultato verrà memorizzato nella cache (a meno che tu non lo stia utilizzando iterator
), e quindi sarà preferibile utilizzarlo len
, poiché questo evita di colpire nuovamente il database e anche la possibilità di recuperare un diverso numero di risultati !) .
Se stai usando iterator
, allora ti suggerirei di includere una variabile di conteggio mentre ripeti (invece di usare count) per gli stessi motivi.
La scelta tra len()
e count()
dipende dalla situazione e vale la pena capire a fondo come funzionano per usarli correttamente.
Lascia che ti fornisca alcuni scenari:
(molto importante) Quando vuoi solo conoscere il numero di elementi e non prevedi di elaborarli in alcun modo, è fondamentale utilizzare count()
:
DO: queryset.count()
- questo eseguirà una singola SELECT COUNT(*) some_table
query, tutti i calcoli vengono eseguiti sul lato RDBMS, Python deve solo recuperare il numero del risultato con un costo fisso di O (1)
NON: len(queryset)
- questo eseguirà una SELECT * FROM some_table
query, recuperando l'intera tabella O (N) e richiedendo memoria O (N) aggiuntiva per memorizzarla. Questo è il peggio che si possa fare
Quando si intende comunque recuperare il set di query è leggermente migliore da usare, len()
che non causerà una query di database aggiuntiva come count()
farebbe:
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
Contare:
queryset.count() # this will perform an extra db query - len() did not
for obj in queryset: # fetching data
pass
Secondo caso ripristinato (quando il set di query è già stato recuperato):
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)
Tutto sarà chiaro una volta che darai un'occhiata "sotto il cofano":
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)
Buoni riferimenti nei documenti di Django:
QuerySet
implementazione contestualmente.
Penso che l'uso len(qs)
abbia più senso qui in quanto è necessario iterare sui risultati. qs.count()
è un'opzione migliore se tutto ciò che vuoi fare è stampare il conteggio e non iterare sui risultati.
len(qs)
colpirà il database con select * from table
mentre qs.count()
colpirà il db con select count(*) from table
.
inoltre qs.count()
darà un numero intero di ritorno e non è possibile iterare su di esso
Per le persone che preferiscono le misurazioni di prova (Postresql):
Se abbiamo un modello Persona semplice e 1000 istanze di esso:
class Person(models.Model):
name = models.CharField(max_length=100)
age = models.SmallIntegerField()
def __str__(self):
return self.name
Nel caso medio dà:
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)
Quindi, come puoi vedere count()
quasi 2 volte più velocemente rispetto len()
a questo particolare caso di prova.
Riassumendo ciò che gli altri hanno già risposto:
len()
recupererà tutti i record e li ripeterà.count()
eseguirà un'operazione SQL COUNT (molto più veloce quando si ha a che fare con un set di query di grandi dimensioni).È anche vero che se dopo questa operazione l'intero set di query verrà iterato, nel suo insieme potrebbe essere leggermente più efficiente da usare len()
.
però
In alcuni casi, ad esempio quando si hanno limitazioni di memoria, potrebbe essere conveniente (quando possibile) suddividere l'operazione eseguita sui record. Ciò può essere ottenuto utilizzando l' impaginazione django .
Quindi, utilizzare count()
sarebbe la scelta e potresti evitare di dover recuperare l'intero set di query in una volta.