Django QuerySet üzerinde len vs sayma


93

Django'da, QuerySetüzerinde yineleyeceğim ve sonuçlarını yazdıracağım bir a sahip olduğum için, nesneleri saymak için en iyi seçenek nedir? len(qs)veya qs.count()?

(Aynı yinelemedeki nesneleri saymanın bir seçenek olmadığı da göz önüne alındığında.)


2
İlginç soru. Bunun profilini çıkarmanızı öneririm .. Çok ilgilenirim! Tam olarak değerlendirilmiş nesnelerdeki len () 'in çok fazla ek yükü olup olmadığını bilmek için python hakkında yeterince bilgim yok. Saymaktan daha hızlı olabilir!
Yuji 'Tomita' Tomita

Yanıtlar:


132

Django belgeleri aşağıdakilerdencount ziyade kullanmanızı tavsiye etse de len:

Not: Yapmak len()istediğiniz tek şey kümedeki kayıt sayısını belirlemekse QuerySets üzerinde kullanmayın . SELECT COUNT(*)SQL'leri kullanarak veritabanı düzeyinde bir sayımı işlemek çok daha etkilidir ve Django count()tam da bu nedenle bir yöntem sağlar .

Yine de bu Sorgu Kümesi yineleme olduğundan, sonuç önbelleğe edilecektir (kullandığınız sürece iterator) ve kullanımı tercih edilebilir bu yüzden lenberi, bu kaçınır muhtemelen farklı bir sonuç sayısı alınırken de yine veritabanını isabet ve !) .
Eğer kullanıyorsanız iterator, aynı nedenlerle (count kullanmak yerine) yinelediğinizde bir sayma değişkeni eklemenizi öneririm.


60

Arasında seçim yapmak len()ve count()duruma bağlıdır ve bunları doğru kullanmak için nasıl çalıştıklarını derinlemesine anlamaya değer.

Size birkaç senaryo sunmama izin verin:

  1. (en önemlisi) Yalnızca öğelerin sayısını bilmek istiyorsanız ve bunları herhangi bir şekilde işlemeyi planlamıyorsanız, kullanmak çok önemlidir count():

    YAPIN: queryset.count() - bu tek bir SELECT COUNT(*) some_tablesorgu gerçekleştirir , tüm hesaplamalar RDBMS tarafında yapılır, Python'un sonuç numarasını sabit O (1) maliyetiyle alması gerekir.

    YAPMAYIN: len(queryset) - bu SELECT * FROM some_tablesorgu gerçekleştirecek , tüm O (N) tablosunu getirecek ve saklamak için ek O (N) belleği gerektirecektir. Bu yapılabilecek en kötü şey

  2. Yine de len()sorgu kümesini getirmeyi düşündüğünüzde, kullanılması biraz daha iyidir; bu, aşağıdaki gibi fazladan bir veritabanı sorgusuna neden count()olmaz:

    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
    

    Miktar:

    queryset.count() # this will perform an extra db query - len() did not
    
    for obj in queryset: # fetching data
        pass
    
  3. 2. durum geri alındı ​​(sorgu kümesi zaten getirildiğinde):

    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)
    

"Kaputun altına" bir göz attığınızda her şey netleşecek:

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 belgelerinde iyi referanslar:


5
Mükemmel yanıt, QuerySetuygulamayı bağlamsal olarak yayınlamak için +1 .
nehem

4
Kelimenin tam anlamıyla mükemmel bir cevap. Neyin kullanılacağını ve daha da önemlisi neden kullanımının açıklanması.
Tom Pegler

28

len(qs)Sonuçları yinelemeniz gerektiğinden kullanmanın burada daha mantıklı olduğunu düşünüyorum . qs.count()Eğer yapmak istediğiniz her şey sayımı yazdırıyorsa ve sonuçları yinelemiyorsa daha iyi bir seçenektir.

len(qs)ile veritabanına vuracak, select * from tableoysa ile db'ye qs.count()vuracak select count(*) from table.

ayrıca qs.count()dönüş tamsayı verir ve üzerinde yineleme yapamazsınız


3

Test ölçümlerini tercih eden kişiler için (Postresql):

Basit bir Kişi modelimiz ve bunun 1000 örneğine sahipsek:

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

    def __str__(self):
        return self.name

Ortalama durumda şunları verir:

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)

Peki , bu özel durumdan count()neredeyse 2 kat daha hızlı nasıl görebilirsiniz?len()


0

Başkalarının zaten yanıtladığını özetlemek:

  • len() tüm kayıtları alacak ve üzerinde yineleyecek.
  • count() SQL COUNT işlemi gerçekleştirecek (büyük sorgu kümesiyle çalışırken çok daha hızlı).

Bu işlemden sonra tüm sorgu kümesinin yinelenmesi durumunda, bir bütün olarak kullanımının biraz daha verimli olabileceği de doğrudur len().

ancak

Bazı durumlarda, örneğin bellek sınırlamaları varken, kayıtlar üzerinde gerçekleştirilen işlemi bölmek (mümkün olduğunda) uygun olabilir. Bu, django sayfalama kullanılarak elde edilebilir .

Daha sonra kullanmak count()seçim olacaktır ve tüm sorgu kümesini bir kerede almak zorunda kalmayabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.