web-dev-qa-db-fra.com

Compter vs len sur un Django QuerySet

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

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

71
antonagestam

Bien que les documents Django recommandent d'utiliser count plutôt que len:

Remarque: N'utilisez pas len() sur QuerySets si tout ce que vous voulez faire est de déterminer le nombre d'enregistrements dans l'ensemble. Il est beaucoup plus efficace de gérer un décompte au niveau de la base de données, en utilisant SELECT COUNT(*) de SQL, et Django fournit une méthode count() précisément pour cette raison.

Puisque vous itérez de toute façon ce QuerySet, le résultat sera mis en cache (sauf si vous utilisez iterator ), et il sera donc préférable d'utiliser len, car cela évite de frapper à nouveau la base de données, et aussi la possibilité de récupérer un nombre différent de résultats !).
Si vous utilisez iterator, je suggérerais d'inclure une variable de comptage à mesure que vous parcourez (plutôt que d'utiliser le comptage) pour les mêmes raisons.

105
Andy Hayden

Choisir entre len() et count() dépend de la situation et il vaut la peine de comprendre en profondeur comment ils fonctionnent pour les utiliser correctement.

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

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

    FAIRE: queryset.count() - cela effectuera une seule requête SELECT COUNT(*) some_table, tous les calculs sont effectués côté RDBMS, = Python a juste besoin de récupérer le numéro de résultat avec un coût fixe de O (1)

    NE PAS: len(queryset) - cela exécutera la requête SELECT * FROM some_table, Récupérant la table entière O(N) et nécessitant une mémoire supplémentaire O(N) pour le stocker. C'est le pire qui puisse être fait

  2. De toute façon, lorsque vous avez l'intention de récupérer le jeu de requêtes, il est légèrement préférable d'utiliser len(), ce qui ne provoquera pas de requête de base de données supplémentaire car 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
    

    Compter:

    queryset.count() # this will perform an extra db query - len() did not
    
    for obj in queryset: # fetching data
        pass
    
  3. Deuxième cas inversé (lorsque l'ensemble de requêtes 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 Django docs:

36
Krzysiek

Je pense que l'utilisation de len(qs) est plus logique ici car vous devez répéter les résultats. qs.count() est une meilleure option si tout ce que vous voulez faire affiche le nombre et ne pas répéter les résultats.

len(qs) frappera la base de données avec select * from table tandis que qs.count() frappera la base de données avec select count(*) from table.

également qs.count() donnera un entier de retour et vous ne pouvez pas itérer dessus

24
Rohan