No Django, dado que tenho um QuerySetque 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 QuerySetque 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 countvez 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_tableconsulta ú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_tableconsulta, 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:
QuerySetimplementaçã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 tableenquanto 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.