En Django, dado que tengo un sobre el QuerySet
que voy a iterar e imprimir los resultados, ¿cuál es la mejor opción para contar los objetos? len(qs)
o qs.count()
?
(También dado que contar los objetos en la misma iteración no es una opción).
python
django
performance
antonagestam
fuente
fuente
Respuestas:
Aunque los documentos de Django recomiendan usar en
count
lugar delen
:Dado que está iterando este QuerySet de todos modos, el resultado se almacenará en caché (a menos que lo esté usando
iterator
), por lo que será preferible usarlolen
, ya que esto evita volver a golpear la base de datos, ¡y también la posibilidad de recuperar un número diferente de resultados !) .Si está utilizando
iterator
, le sugiero que incluya una variable de recuento a medida que itera (en lugar de utilizar el recuento) por las mismas razones.fuente
Elegir entre
len()
ycount()
depende de la situación y vale la pena comprender profundamente cómo funcionan para usarlos correctamente.Permítanme brindarles algunos escenarios:
(lo más importante) Cuando solo desea saber la cantidad de elementos y no planea procesarlos de ninguna manera, es crucial usar
count()
:HACER:
queryset.count()
esto realizará unaSELECT COUNT(*) some_table
consulta única , todo el cálculo se realiza en el lado de RDBMS, Python solo necesita recuperar el número de resultado con un costo fijo de O (1)NO HACER:
len(queryset)
- esto realizará unaSELECT * FROM some_table
consulta, recuperando la tabla completa O (N) y requiriendo memoria O (N) adicional para almacenarla. Esto es lo peor que se puede hacerCuando tenga la intención de obtener el conjunto de consultas de todos modos, es un poco mejor usarlo,
len()
lo que no causará una consulta de base de datos adicional como locount()
haría: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
Contar:
queryset.count() # this will perform an extra db query - len() did not for obj in queryset: # fetching data pass
Segundo caso revertido (cuando el conjunto de consultas ya se ha obtenido):
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)
Todo quedará claro una vez que eche un vistazo "debajo del capó":
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)
Buenas referencias en los documentos de Django:
fuente
QuerySet
implementación contextualmente.Creo que el uso
len(qs)
tiene más sentido aquí, ya que necesita iterar sobre los resultados.qs.count()
es una mejor opción si todo lo que quieres hacer es imprimir el recuento y no iterar sobre los resultados.len(qs)
golpeará la base de datos conselect * from table
mientras que golpeará la base de datosqs.count()
conselect count(*) from table
.también
qs.count()
dará un entero de retorno y no puede iterar sobre élfuente
Para las personas que prefieren las mediciones de prueba (Postresql):
Si tenemos un modelo de persona simple y 1000 instancias de él:
class Person(models.Model): name = models.CharField(max_length=100) age = models.SmallIntegerField() def __str__(self): return self.name
En caso medio da:
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)
Entonces, ¿cómo puede ver
count()
casi 2 veces más rápido quelen()
en este caso de prueba en particular?fuente
Resumiendo lo que otros ya han respondido:
len()
buscará todos los registros e iterará sobre ellos.count()
realizará una operación SQL COUNT (mucho más rápido cuando se trata de un gran conjunto de consultas).También es cierto que si después de esta operación, se iterará todo el conjunto de consultas, entonces, en su conjunto, podría ser un poco más eficiente de usar
len()
.sin embargo
En algunos casos, por ejemplo cuando hay limitaciones de memoria, puede ser conveniente (cuando sea posible) dividir la operación realizada entre los registros. Eso se puede lograr usando la paginación de django .
Entonces, usar
count()
sería la elección y podría evitar tener que buscar todo el conjunto de consultas a la vez.fuente