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.)
python
django
performance
antonagestam
fonte
fonte
Respostas:
Embora os documentos do Django recomendem usar em
count
vez delen
: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 usarlen
, 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.fonte
A escolha entre
len()
ecount()
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á umaSELECT 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á aSELECT * 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 feitoQuando 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 comocount()
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:
fonte
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 comselect * from table
enquantoqs.count()
irá atingir o banco de dados comselect count(*) from table
.também
qs.count()
fornecerá um inteiro de retorno e você não pode iterar sobre elefonte
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 quelen()
neste caso de teste específico.fonte
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.fonte