在Django QuerySet上计算vs len


92

在Django中,假设我QuerySet要遍历并打印结果,那么计算对象的最佳选择是什么?len(qs)还是qs.count()

(此外,考虑到在相同的迭代中对对象进行计数也是不可行的。)


2
有趣的问题。我建议对此进行剖析..我会很感兴趣!我对python的了解不足,无法确定完全评估的对象上的len()是否有很多开销。它可能比计数更快!
Yuji'Tomita'Tomita 2013年

Answers:


131

尽管Django文档建议使用count而不是len

注意:len()如果要确定的是记录集中的记录数,请不要在QuerySet上使用。使用SQL的在数据库级别处理计数效率更高SELECT COUNT(*),而Djangocount()为此提供了一种方法。

由于无论如何都要迭代此QuerySet,结果将被缓存(除非使用iterator),因此最好使用len,因为这样可以避免再次访问数据库,也可以避免获取不同数量的结果!) 。
如果您使用iterator,则出于相同的原因,我建议您在遍历(而不是使用count)时包括一个计数变量。


60

len()和之间进行选择count()取决于情况,有必要深入了解它们如何正确使用它们。

让我为您提供一些方案:

  1. (最关键的)当您只想知道元素的数量并且不打算以任何方式处理它们时,至关重要的是使用count()

    要做: queryset.count() -这将执行单个SELECT COUNT(*) some_table查询,所有计算都在RDBMS端进行,Python只需要以固定成本O(1)检索结果编号。

    不要: len(queryset) -这将执行SELECT * FROM some_table查询,获取整个表O(N),并需要额外的O(N)内存来存储它。这是最糟糕的事情

  2. 无论如何,当您打算获取len()查询集时,最好使用它,这样不会引起额外的数据库查询count()

    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
    

    计数:

    queryset.count() # this will perform an extra db query - len() did not
    
    for obj in queryset: # fetching data
        pass
    
  3. 恢复第二种情况(已获取查询集时):

    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)
    

一眼就能看到一切:

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)

Django文档中的良好参考:


5
精采的答案,+ 1用于根据QuerySet上下文发布实现。
nehem

4
从字面上看,这是一个完美的答案。解释使用什么,更重要的是解释为什么使用。
汤姆·佩格勒

28

我认为len(qs)在这里使用更有意义,因为您需要迭代结果。qs.count()如果您要执行的所有操作都可以打印计数而不对结果进行迭代,则是更好的选择。

len(qs)将以命中数据库,select * from tableqs.count()将以命中数据库select count(*) from table

qs.count()将给出返回整数,您不能对其进行迭代


3

对于喜欢测试测量的人(Postresql):

如果我们有一个简单的Person模型及其1000个实例:

class Person(models.Model):
    name = models.CharField(max_length=100)
    age = models.SmallIntegerField()

    def __str__(self):
        return self.name

平均而言,它给出:

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)

因此,您如何看到比该特定测试用例快count()2倍的速度len()


0

总结其他人已经回答的问题:

  • len() 将获取所有记录并对其进行迭代。
  • count() 将执行SQL COUNT操作(处理大型查询集时要快得多)。

的确,如果在执行此操作之后,整个查询集将被迭代,那么从整体上来说,使用它可能会稍微更有效len()

然而

在某些情况下,例如,在有内存限制的情况下,可以方便地(在可行时)拆分对记录执行的操作。可以使用django分页来实现。

然后,使用count()将是一种选择,并且您可以避免必须一次获取整个查询集。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.