Dans Django, étant donné que j'ai un sur QuerySet
lequel je vais parcourir et imprimer les résultats, quelle est la meilleure option pour compter les objets? len(qs)
ou qs.count()
?
(Aussi étant donné que compter les objets dans la même itération n'est pas une option.)
python
django
performance
Antonagestam
la source
la source
Réponses:
Bien que la documentation Django recommande d'utiliser
count
plutôt quelen
:Puisque vous itérez de toute façon ce QuerySet, le résultat sera mis en cache (sauf si vous l'utilisez
iterator
), et il sera donc préférable de l'utiliserlen
, 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
, alors je suggérerais d'inclure une variable de comptage au fur et à mesure que vous parcourez (plutôt que d'utiliser count) pour les mêmes raisons.la source
Le choix entre
len()
etcount()
dépend de la situation et il vaut la peine de comprendre profondément comment ils fonctionnent pour les utiliser correctement.Laissez-moi vous présenter quelques scénarios:
(le plus crucial) Lorsque vous souhaitez uniquement 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 essentiel d'utiliser
count()
:ACTION:
queryset.count()
- cela effectuera une seuleSELECT COUNT(*) some_table
requête, tous les calculs sont effectués côté SGBDR, 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 effectuera uneSELECT * FROM some_table
requête, récupérant toute la table O (N) et nécessitant de la mémoire O (N) supplémentaire pour la stocker. C'est le pire qui puisse être faitLorsque vous avez l'intention de récupérer le jeu de requêtes de toute façon, il est légèrement préférable de l'utiliser,
len()
ce qui ne provoquera pas une requête de base de données supplémentaire comme lecount()
ferait: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
2ème cas annulé (lorsque le jeu 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 la documentation Django:
la source
QuerySet
implémentation contextuelle.Je pense que l'utilisation
len(qs)
est plus logique ici car vous devez parcourir les résultats.qs.count()
est une meilleure option si tout ce que vous voulez faire imprimer le décompte et ne pas parcourir les résultats.len(qs)
frappera la base de données avecselect * from table
alors queqs.count()
frappera la base de données avecselect count(*) from table
.qs.count()
donnera également un entier de retour et vous ne pourrez pas le parcourirla source
Pour les personnes qui préfèrent les mesures de test (Postresql):
Si nous avons un modèle Person 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
En moyenne, 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 2 fois plus vite quelen()
dans ce cas de test particulier.la source
Résumant ce que les autres ont déjà répondu:
len()
va chercher tous les enregistrements et les parcourir.count()
effectuera une opération SQL COUNT (beaucoup plus rapide en cas de gros jeu de requêtes).Il est également vrai que si après cette opération, l'ensemble du jeu de requêtes sera itéré, alors dans son ensemble, il pourrait être légèrement plus efficace à utiliser
len()
.pourtant
Dans certains cas, par exemple lorsque la mémoire est limitée, il peut être pratique (si possible) de fractionner l'opération effectuée sur les enregistrements. Cela peut être réalisé en utilisant la pagination django .
Ensuite, utiliser
count()
serait le choix et vous pourriez éviter d'avoir à récupérer l'ensemble de la requête en une seule fois.la source