119 votes

Count vs len sur un QuerySet Django

Dans Django, étant donné que j'ai un QuerySet sur lequel je vais itérer et dont je vais imprimer les résultats, quelle est la meilleure option pour compter les objets ? len(qs) o qs.count() ?

(Sachant également que compter les objets dans la même itération n'est pas une option).

160voto

Andy Hayden Points 38010

Bien que le Documentation sur Django recommande d'utiliser count plutôt que len :

Note : N'utilisez pas len() sur les QuerySets si tout ce que vous voulez faire est de déterminer le nombre d'enregistrements dans le jeu. Il est beaucoup plus efficace de gérer un comptage au niveau de la base de données, en utilisant la fonction SQL SELECT COUNT(*) et Django fournit un count() pour cette raison précise.

Puisque vous itérez ce QuerySet de toute façon, le résultat sera mis en cache (sauf si vous utilisez iterator ), et il sera donc préférable d'utiliser l'option len puisque cela permet d'éviter d'interroger à nouveau la base de données et de récupérer éventuellement un nombre différent de résultats. !).
Si vous utilisez iterator alors je suggère d'inclure une variable de comptage lors de l'itération (plutôt que d'utiliser count) pour les mêmes raisons.

126voto

Krzysiek Points 2277

Choisir entre len() y count() dépend de la situation et il est utile de bien comprendre leur fonctionnement pour les utiliser correctement.

Permettez-moi de vous présenter quelques scénarios :

  1. (le plus crucial) Lorsque vous voulez seulement connaître le nombre d'éléments et que vous ne prévoyez pas de les traiter d'une quelconque manière, il est crucial d'utiliser count() :

DO : queryset.count() - ce qui permettra d'effectuer un seul SELECT COUNT(*) FROM some_table tous les calculs sont effectués du côté du SGBDR, Python n'a plus qu'à récupérer le numéro de résultat avec un coût fixe de O(1).

NE LE FAITES PAS : len(queryset) - cela permettra de réaliser SELECT * FROM some_table requête, en récupérant la table entière O(N) et en nécessitant O(N) de mémoire supplémentaire pour la stocker. C'est le pire qu'on puisse faire

  1. Lorsque vous avez l'intention de récupérer le queryset de toute façon, il est légèrement préférable d'utiliser len() ce qui n'entraînera pas de requête supplémentaire dans la base de données, comme c'est le cas pour count() serait

len() (une seule requête de la base de données)

    len(queryset) # SELECT * 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

count() (deux requêtes de la base de données !) :

    queryset.count() # First db query SELECT COUNT(*)

    for obj in queryset: # Second db query (fetching data) SELECT *
        pass
  1. Rétablissement du deuxième cas (lorsque le queryset a déjà été récupéré) :

     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)

Tout sera clair une fois que vous aurez jeté un coup d'œil "sous le capot" :

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)

Bonnes références dans la documentation de Django :

29voto

Rohan Points 518

Je pense qu'utiliser len(qs) a plus de sens ici car vous devez itérer sur les résultats. qs.count() est une meilleure option si tout ce que vous voulez faire est d'imprimer le compte et ne pas itérer sur les résultats.

len(qs) va frapper la base de données avec select * from table alors que qs.count() touchera la base de données avec select count(*) from table .

également qs.count() retournera un entier et vous ne pourrez pas itérer dessus

6voto

funnydman Points 2947

Pour les personnes qui préfèrent les mesures d'essai (Postresql) :

Si nous avons un modèle de personne simple et 1000 instances de celui-ci :

class Person(models.Model):
    name = models.CharField(max_length=100)
    age = models.SmallIntegerField()

    def __str__(self):
        return self.name

Dans le cas moyen, cela donne :

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)

Alors comment pouvez-vous voir count() presque 2x plus vite que len() dans ce cas particulier de test.

3voto

Pablo Guerrero Points 183

Résumer ce que d'autres ont déjà répondu :

  • len() va récupérer tous les enregistrements et les parcourir.
  • count() exécutera une opération SQL COUNT (beaucoup plus rapide lorsqu'il s'agit de grands ensembles de requêtes).

Il est également vrai que si, après cette opération, l'ensemble des querelles est itéré, il pourrait être légèrement plus efficace d'utiliser len() .

Cependant

Dans certains cas, par exemple en cas de limitation de la mémoire, il peut être utile (si possible) de diviser l'opération effectuée sur les enregistrements. Cela peut être réalisé en utilisant pagination django .

Ensuite, en utilisant count() serait le choix qui s'impose et vous pourriez éviter de devoir récupérer l'ensemble des requêtes en une seule fois.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X