Hitung vs len pada Django QuerySet

93

Di Django, diberikan bahwa saya memiliki QuerySetyang akan saya ulangi dan mencetak hasilnya, apa pilihan terbaik untuk menghitung objek? len(qs)atau qs.count()?

(Juga mengingat bahwa menghitung objek dalam iterasi yang sama bukanlah suatu pilihan.)

antonagestam
sumber
2
Pertanyaan menarik. Saya sarankan membuat profil ini .. Saya akan sangat tertarik! Saya tidak cukup tahu tentang python untuk mengetahui apakah len () pada objek yang dievaluasi sepenuhnya memiliki banyak overhead. Mungkin lebih cepat dari hitungan!
Yuji 'Tomita' Tomita

Jawaban:

132

Meskipun dokumen Django merekomendasikan penggunaan countdaripada len:

Catatan: Jangan gunakan len()di QuerySets jika yang ingin Anda lakukan hanyalah menentukan jumlah rekaman dalam kumpulan. Jauh lebih efisien menangani hitungan pada tingkat basis data, menggunakan SQL SELECT COUNT(*), dan Django menyediakan count()metode tepat untuk alasan ini.

Karena Anda tetap mengulang QuerySet ini, hasilnya akan disimpan dalam cache (kecuali jika Anda menggunakannya iterator), dan karenanya akan lebih disukai untuk digunakan len, karena ini menghindari memukul database lagi, dan juga kemungkinan mengambil jumlah hasil yang berbeda !) .
Jika Anda menggunakan iterator, maka saya akan menyarankan untuk memasukkan variabel penghitungan saat Anda mengulang (daripada menggunakan hitungan) untuk alasan yang sama.

Andy Hayden
sumber
60

Memilih antara len()dan count()bergantung pada situasinya dan sangatlah penting untuk memahami secara mendalam cara kerjanya untuk menggunakannya dengan benar.

Izinkan saya memberi Anda beberapa skenario:

  1. (paling penting) Ketika Anda hanya ingin mengetahui jumlah elemen dan Anda tidak berencana untuk memprosesnya dengan cara apa pun, penting untuk menggunakan count():

    LAKUKAN: queryset.count() - ini akan melakukan SELECT COUNT(*) some_tablekueri tunggal , semua komputasi dilakukan di sisi RDBMS, Python hanya perlu mengambil nomor hasil dengan biaya tetap O (1)

    JANGAN: len(queryset) - ini akan melakukan SELECT * FROM some_tablekueri, mengambil seluruh tabel O (N) dan membutuhkan memori O (N) tambahan untuk menyimpannya. Ini adalah hal terburuk yang bisa dilakukan

  2. Saat Anda bermaksud untuk mengambil queryset, sebaiknya gunakan len()yang tidak akan menyebabkan kueri database tambahan seperti 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
    

    Menghitung:

    queryset.count() # this will perform an extra db query - len() did not
    
    for obj in queryset: # fetching data
        pass
    
  3. Kasus kedua yang dikembalikan (saat queryset telah diambil):

    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)
    

Semuanya akan menjadi jelas setelah Anda melirik "di bawah tenda":

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)

Referensi bagus dalam dokumen Django:

Krzysiek
sumber
5
Jawaban brilian, +1 untuk memposting QuerySetimplementasi secara kontekstual.
nehem
4
Jawaban yang benar-benar sempurna. Menjelaskan apa yang harus digunakan dan, yang lebih penting, mengapa juga menggunakannya.
Tom Pegler
28

Saya pikir menggunakan len(qs)lebih masuk akal di sini karena Anda perlu mengulang hasil. qs.count()adalah pilihan yang lebih baik jika semua yang Anda ingin lakukan itu mencetak hitungan dan tidak mengulang hasil.

len(qs)akan masuk ke database dengan select * from tablesedangkan qs.count()akan menekan db dengan select count(*) from table.

juga qs.count()akan memberikan bilangan bulat kembali dan Anda tidak dapat mengulanginya

Rohan
sumber
3

Untuk orang yang lebih suka pengukuran tes (Postresql):

Jika kita memiliki model Person sederhana dan 1000 contoh darinya:

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

    def __str__(self):
        return self.name

Dalam kasus rata-rata itu memberi:

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)

Jadi bagaimana Anda bisa melihat count()hampir 2x lebih cepat daripada len()dalam kasus pengujian khusus ini.

pelawak
sumber
0

Meringkas apa yang telah dijawab orang lain:

  • len() akan mengambil semua catatan dan mengulanginya.
  • count() akan melakukan operasi SQL COUNT (jauh lebih cepat saat menangani queryset besar).

Juga benar bahwa jika setelah operasi ini, seluruh queryset akan diulangi, maka secara keseluruhan itu bisa sedikit lebih efisien untuk digunakan len().

Namun

Dalam beberapa kasus, misalnya ketika memiliki keterbatasan memori, akan lebih mudah (jika memungkinkan) untuk membagi operasi yang dilakukan pada record. Itu bisa dicapai dengan menggunakan pagination django .

Kemudian, menggunakan count()akan menjadi pilihan dan Anda bisa menghindari harus mengambil seluruh queryset sekaligus.

Pablo Guerrero
sumber