Count vs len su un Django QuerySet


93

In Django, dato che ho un file su QuerySetcui 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.)


2
Domanda interessante. Suggerisco di profilare questo .. Sarei molto interessato! Non so abbastanza su Python per sapere se len () su un oggetto completamente valutato ha molto overhead. Potrebbe essere più veloce del conteggio!
Yuji 'Tomita' Tomita

Risposte:


132

Sebbene i documenti di Django consigliano di utilizzare countanziché 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 SQL SELECT COUNT(*), e Django fornisce un count()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.


60

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:

  1. (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_tablequery, 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_tablequery, recuperando l'intera tabella O (N) e richiedendo memoria O (N) aggiuntiva per memorizzarla. Questo è il peggio che si possa fare

  2. 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
    
  3. 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:


5
Risposta brillante, +1 per aver pubblicato l' QuerySetimplementazione contestualmente.
nehem,

4
Letteralmente la risposta perfetta. Spiegare cosa usare e, soprattutto, anche il motivo dell'utilizzo.
Tom Pegler

28

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 tablementre 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


3

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.


0

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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.