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.)
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 utilisantSELECT COUNT(*)
de SQL, et Django fournit une méthodecount()
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.
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:
(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
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
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:
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