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).
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).
Respuestas:
Aunque los documentos de Django recomiendan usar en count
lugar de len
:
Nota: No utilice
len()
en QuerySets si todo lo que desea hacer es determinar la cantidad de registros en el conjunto. Es mucho más eficiente manejar un recuento a nivel de la base de datos, usando SQLSELECT COUNT(*)
, y Django proporciona uncount()
método precisamente por esta razón.
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 usarlo len
, 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.
Elegir entre len()
y count()
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á una SELECT 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á una SELECT * 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 hacer
Cuando 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 lo count()
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:
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 con select * from table
mientras que golpeará la base de datos qs.count()
con select count(*) from table
.
también qs.count()
dará un entero de retorno y no puede iterar sobre él
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 que len()
en este caso de prueba en particular?
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.