No Django, dado que tenho um QuerySet
que irei iterar e imprimir os resultados, qual a melhor opção para contar os objetos? len(qs)
ou qs.count()
?
(Além disso, considerando que contar os objetos na mesma iteração não é uma opção.)
No Django, dado que tenho um QuerySet
que irei iterar e imprimir os resultados, qual a melhor opção para contar os objetos? len(qs)
ou qs.count()
?
(Além disso, considerando que contar os objetos na mesma iteração não é uma opção.)
Respostas:
Embora os documentos do Django recomendem usar em count
vez de len
:
Nota: Não use
len()
em QuerySets se tudo o que você deseja fazer é determinar o número de registros no conjunto. É muito mais eficiente lidar com uma contagem no nível do banco de dados, usando SQLSELECT COUNT(*)
, e o Django fornece umcount()
método exatamente por esse motivo.
Já que você está iterando este QuerySet de qualquer maneira, o resultado será armazenado em cache (a menos que você esteja usando iterator
), e por isso será preferível usar len
, pois isso evita atingir o banco de dados novamente e também a possibilidade de recuperar um número diferente de resultados !) .
Se você estiver usando iterator
, sugiro incluir uma variável de contagem conforme você itera (em vez de usar a contagem) pelos mesmos motivos.
A escolha entre len()
e count()
depende da situação e vale a pena entender profundamente como funcionam para usá-los corretamente.
Deixe-me apresentar alguns cenários:
(o mais crucial) Quando você deseja apenas saber o número de elementos e não planeja processá-los de forma alguma, é crucial usar count()
:
FAZER: queryset.count()
- isso executará uma SELECT COUNT(*) some_table
consulta única , toda a computação é realizada no lado do RDBMS, o Python só precisa recuperar o número do resultado com custo fixo de O (1)
NÃO FAÇA: len(queryset)
- isso executará a SELECT * FROM some_table
consulta, obtendo toda a tabela O (N) e exigindo memória O (N) adicional para armazená-la. Isso é o pior que pode ser feito
Quando você pretende buscar o queryset de qualquer maneira, é um pouco melhor usar o len()
que não causará uma consulta extra ao banco de dados como count()
faria:
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
Contagem:
queryset.count() # this will perform an extra db query - len() did not
for obj in queryset: # fetching data
pass
2º caso revertido (quando o queryset já foi buscado):
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)
Tudo ficará claro quando você der uma olhada "sob o 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)
Boas referências na documentação do Django:
QuerySet
implementação contextualmente.
Acho que usar len(qs)
faz mais sentido aqui, pois você precisa iterar os resultados. qs.count()
é uma opção melhor se tudo o que você deseja fazer imprima a contagem e não itere sobre os resultados.
len(qs)
irá atingir o banco de dados com select * from table
enquanto qs.count()
irá atingir o banco de dados com select count(*) from table
.
também qs.count()
fornecerá um inteiro de retorno e você não pode iterar sobre ele
Para pessoas que preferem medições de teste (Postresql):
Se tivermos um modelo Person simples e 1000 instâncias dele:
class Person(models.Model):
name = models.CharField(max_length=100)
age = models.SmallIntegerField()
def __str__(self):
return self.name
No caso médio, dá:
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)
Então, como você pode ver count()
quase 2x mais rápido do que len()
neste caso de teste específico.
Resumindo o que outros já responderam:
len()
irá buscar todos os registros e iterar sobre eles.count()
irá realizar uma operação SQL COUNT (muito mais rápida ao lidar com um grande queryset).Também é verdade que, se após essa operação, todo o queryset for iterado, então, como todo, ele pode ser um pouco mais eficiente de usar len()
.
Contudo
Em alguns casos, por exemplo, quando há limitações de memória, pode ser conveniente (quando possível) dividir a operação realizada sobre os registros. Isso pode ser feito usando a paginação django .
Então, usar count()
seria a escolha e você poderia evitar ter que buscar o queryset inteiro de uma vez.