Đếm so với len trên Django QuerySet


93

Trong Django, cho rằng tôi có một QuerySetmà tôi sẽ lặp lại và in kết quả của nó, tùy chọn tốt nhất để đếm các đối tượng là gì? len(qs)hoặc qs.count()?

(Ngoài ra, việc đếm các đối tượng trong cùng một lần lặp không phải là một tùy chọn.)


2
Câu hỏi thú vị. Tôi đề nghị lập hồ sơ này .. Tôi rất quan tâm! Tôi không biết đủ về python để biết liệu len () trên một đối tượng được đánh giá đầy đủ có nhiều chi phí hay không. Nó có thể nhanh hơn đếm!
Yuji 'Tomita' Tomita

Câu trả lời:


132

Mặc dù tài liệu Django khuyên bạn nên sử dụng counthơn là len:

Lưu ý: Không sử dụng len()trên QuerySets nếu tất cả những gì bạn muốn làm là xác định số lượng bản ghi trong tập hợp. Sẽ hiệu quả hơn nhiều khi xử lý số lượng ở cấp cơ sở dữ liệu, bằng cách sử dụng SQL SELECT COUNT(*)và Django cung cấp một count()phương pháp cho chính xác lý do này.

Vì bạn vẫn đang lặp lại QuerySet này, nên kết quả sẽ được lưu vào bộ nhớ đệm (trừ khi bạn đang sử dụng iterator), và do đó, nó sẽ được ưu tiên sử dụng hơn len, vì điều này tránh đánh lại cơ sở dữ liệu và cũng có thể truy xuất một số kết quả khác !) .
Nếu bạn đang sử dụng iterator, thì tôi khuyên bạn nên bao gồm một biến đếm khi bạn lặp lại (thay vì sử dụng số đếm) vì những lý do tương tự.


60

Lựa chọn giữa len()count() tùy thuộc vào tình huống và cần hiểu sâu sắc cách chúng hoạt động để sử dụng chúng một cách chính xác.

Hãy để tôi cung cấp cho bạn một vài tình huống:

  1. (quan trọng nhất) Khi bạn chỉ muốn biết số lượng phần tử và bạn không có kế hoạch xử lý chúng theo bất kỳ cách nào, điều quan trọng là phải sử dụng count() :

    NÊN: queryset.count() - điều này sẽ thực hiện đơnSELECT COUNT(*) some_table truy vấn , tất cả tính toán được thực hiện ở phía RDBMS, Python chỉ cần truy xuất số kết quả với chi phí cố định là O (1)

    KHÔNG NÊN: len(queryset) - điều này sẽ thực hiện SELECT * FROM some_tabletruy vấn, tìm nạp toàn bộ bảng O (N) và yêu cầu bộ nhớ O (N) bổ sung để lưu trữ nó. Đây là điều tồi tệ nhất có thể được thực hiện

  2. Dù sao thì khi bạn định tìm nạp bộ len()truy vấn count()thì tốt hơn một chút là sử dụng cách này sẽ không gây ra truy vấn cơ sở dữ liệu bổ sung như sau:

    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
    

    Đếm:

    queryset.count() # this will perform an extra db query - len() did not
    
    for obj in queryset: # fetching data
        pass
    
  3. Trường hợp thứ 2 được hoàn nguyên (khi bộ truy vấn đã được tìm nạp):

    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)
    

Mọi thứ sẽ rõ ràng khi bạn xem qua "phần dưới":

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)

Tham khảo tốt trong tài liệu Django:


5
Câu trả lời tuyệt vời, +1 để đăng QuerySettriển khai theo ngữ cảnh.
nehem

4
Câu trả lời hoàn hảo theo đúng nghĩa đen. Giải thích những gì để sử dụng và quan trọng hơn là lý do tại sao sử dụng.
Tom Pegler

28

Tôi nghĩ việc sử dụng len(qs)có ý nghĩa hơn ở đây vì bạn cần phải lặp lại các kết quả. qs.count()là một lựa chọn tốt hơn nếu tất cả những gì bạn muốn thực hiện in ra số lượng và không lặp lại kết quả.

len(qs)sẽ nhấn vào cơ sở dữ liệu select * from tabletrong khi qs.count()sẽ nhấn db với select count(*) from table.

cũng qs.count()sẽ trả về số nguyên và bạn không thể lặp lại nó


3

Đối với những người thích các phép đo thử nghiệm (Postresql):

Nếu chúng ta có một mô hình Người đơn giản và 1000 trường hợp của nó:

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

    def __str__(self):
        return self.name

Trong trường hợp trung bình, nó cho:

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)

Vậy làm thế nào bạn có thể thấy count()gần như 2x nhanh hơn len()trong trường hợp thử nghiệm đặc biệt này.


0

Tóm tắt những gì người khác đã trả lời:

  • len() sẽ tìm nạp tất cả các bản ghi và lặp lại chúng.
  • count() sẽ thực hiện thao tác SQL COUNT (nhanh hơn nhiều khi xử lý bộ truy vấn lớn).

Cũng đúng là nếu sau thao tác này, toàn bộ tập truy vấn sẽ được lặp lại, thì về tổng thể, nó có thể hiệu quả hơn một chút để sử dụng len().

Tuy nhiên

Trong một số trường hợp, chẳng hạn như khi có giới hạn về bộ nhớ, có thể thuận tiện (khi có thể) để phân chia hoạt động được thực hiện trên các bản ghi. Điều đó có thể đạt được bằng cách sử dụng phân trang django .

Sau đó, sử dụng count()sẽ là sự lựa chọn và bạn có thể tránh phải tìm nạp toàn bộ bộ truy vấn cùng một lúc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.