Count vs len ใน Django QuerySet


93

ใน Django เนื่องจากฉันมีสิ่งQuerySetที่ฉันกำลังจะทำซ้ำและพิมพ์ผลลัพธ์ตัวเลือกที่ดีที่สุดสำหรับการนับวัตถุคืออะไร? len(qs)หรือqs.count()?

(นอกจากนี้เนื่องจากการนับวัตถุในการทำซ้ำเดียวกันไม่ใช่ตัวเลือก)


2
คำถามที่น่าสนใจ ฉันแนะนำให้ทำโปรไฟล์นี้ .. ฉันสนใจมาก! ฉันไม่รู้เกี่ยวกับ python มากพอที่จะรู้ว่า len () บนออบเจ็กต์ที่ประเมินอย่างสมบูรณ์มีค่าใช้จ่ายมากหรือไม่ มันอาจจะเร็วกว่าการนับ!
Yuji 'Tomita' Tomita

คำตอบ:


132

แม้ว่าเอกสาร Django จะแนะนำให้ใช้countมากกว่าlen:

หมายเหตุ: อย่าใช้len()กับ QuerySets ถ้าสิ่งที่คุณต้องการทำคือกำหนดจำนวนระเบียนในชุด มีประสิทธิภาพมากกว่าในการจัดการการนับในระดับฐานข้อมูลโดยใช้ SQL SELECT COUNT(*)และ Django ให้count()วิธีการด้วยเหตุผลนี้อย่างแม่นยำ

เนื่องจากคุณกำลังทำ QuerySet นี้ซ้ำผลลัพธ์จะถูกแคช (เว้นแต่คุณจะใช้งานอยู่iterator) ดังนั้นจึงเป็นที่นิยมที่จะใช้lenเนื่องจากวิธีนี้จะหลีกเลี่ยงการกดปุ่มฐานข้อมูลอีกครั้งและอาจดึงผลลัพธ์ที่แตกต่างกันออกไปด้วย !) .
หากคุณกำลังใช้iteratorฉันขอแนะนำให้รวมตัวแปรการนับในขณะที่คุณทำซ้ำ (แทนที่จะใช้การนับ) ด้วยเหตุผลเดียวกัน


60

การเลือกระหว่างlen()และcount()ขึ้นอยู่กับสถานการณ์และควรทำความเข้าใจอย่างลึกซึ้งถึงวิธีการใช้งานอย่างถูกต้อง

ฉันขอเสนอสถานการณ์บางอย่างให้คุณ:

  1. (สำคัญที่สุด) เมื่อคุณต้องการทราบจำนวนองค์ประกอบเท่านั้นและคุณไม่ได้วางแผนที่จะประมวลผลองค์ประกอบเหล่านี้ แต่อย่างใดสิ่งสำคัญคือต้องใช้count():

    DO: 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. เปลี่ยนกลับกรณีที่ 2 (เมื่อมีการดึงข้อมูลชุดสืบค้นแล้ว):

    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
เป็นคำตอบที่สมบูรณ์แบบจริงๆ อธิบายถึงสิ่งที่ต้องใช้และที่สำคัญกว่านั้นคือสาเหตุของการใช้งานด้วย
Tom Pegler

28

ฉันคิดว่าการใช้ตรงlen(qs)นี้มีความหมายมากกว่าเพราะคุณต้องทบทวนผลลัพธ์ qs.count()เป็นตัวเลือกที่ดีกว่าหากทุกสิ่งที่คุณต้องการจะพิมพ์จำนวนนับและไม่วนซ้ำบนผลลัพธ์

len(qs)จะตีฐานข้อมูลด้วยselect * from tableในขณะที่จะตีฐานข้อมูลที่มีqs.count()select count(*) from table

นอกจากนี้ยังqs.count()จะให้ผลตอบแทนจำนวนเต็มและคุณไม่สามารถย้ำมากกว่านั้น


3

สำหรับผู้ที่ชอบการวัดทดสอบ (Postresql):

หากเรามีแบบจำลองบุคคลที่เรียบง่ายและมีอินสแตนซ์ 1,000 รายการ:

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 (เร็วกว่ามากเมื่อจัดการกับ queryset ขนาดใหญ่)

นอกจากนี้ยังเป็นความจริงที่ว่าหากหลังจากการดำเนินการนี้ชุดข้อมูลทั้งหมดจะถูกทำซ้ำดังนั้นโดยรวมแล้วอาจมีประสิทธิภาพในการใช้งานlen()มากกว่า

อย่างไรก็ตาม

ในบางกรณีตัวอย่างเช่นเมื่อมีข้อ จำกัด ของหน่วยความจำอาจเป็นเรื่องสะดวก (เมื่อเป็นไปได้) ในการแยกการดำเนินการที่ดำเนินการกับระเบียน ที่สามารถทำได้โดยใช้เลข Django

จากนั้นการใช้count()จะเป็นทางเลือกและคุณสามารถหลีกเลี่ยงไม่ได้ที่จะต้องดึงข้อมูลชุดแบบสอบถามทั้งหมดพร้อมกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.