ตัวกรอง Django เทียบกับรับวัตถุเดียวหรือไม่


147

ฉันกำลังถกเถียงเรื่องนี้กับเพื่อนร่วมงานบางคน มีวิธีที่ต้องการในการดึงข้อมูลวัตถุใน Django เมื่อคุณต้องการเพียงวัตถุเดียวหรือไม่?

สองวิธีที่ชัดเจนคือ:

try:
    obj = MyModel.objects.get(id=1)
except MyModel.DoesNotExist:
    # We have no object! Do something...
    pass

และ:

objs = MyModel.objects.filter(id=1)

if len(objs) == 1:
    obj = objs[0]
else:
    # We have no object! Do something...
    pass

วิธีแรกดูเหมือนจะมีพฤติกรรมที่ถูกต้องมากขึ้น แต่ใช้ข้อยกเว้นในโฟลว์การควบคุมซึ่งอาจแนะนำค่าใช้จ่ายบางอย่าง ที่สองคือวงเวียนมากขึ้น แต่จะไม่ยกข้อยกเว้น

มีความคิดเห็นใดเกี่ยวกับข้อใดที่เป็นที่นิยมมากกว่านี้? อันไหนมีประสิทธิภาพมากกว่ากัน?

คำตอบ:


177

get()มีให้เฉพาะสำหรับกรณีนี้ ใช้มัน.

ตัวเลือกที่ 2 เกือบจะแม่นยำว่าget()วิธีการใช้งานจริงใน Django ดังนั้นจึงไม่มีความแตกต่าง "ประสิทธิภาพ" (และความจริงที่ว่าคุณกำลังคิดเกี่ยวกับมันแสดงว่าคุณกำลังละเมิดกฎสำคัญของการเขียนโปรแกรมอย่างใดอย่างหนึ่งคือพยายาม ปรับรหัสให้เหมาะสมก่อนที่จะถูกเขียนและทำโปรไฟล์ - จนกว่าคุณจะมีรหัสและสามารถเรียกใช้งานได้คุณไม่รู้ว่ามันจะทำงานอย่างไรและพยายามปรับให้เหมาะสมก่อนหน้านั้นคือเส้นทางแห่งความเจ็บปวด)


ทุกอย่างถูกต้อง แต่ควรมีการเพิ่มข้อมูลเพื่อตอบไหม 1. Python สนับสนุนให้ลอง / ยกเว้น (ดูEAFP ) นั่นคือเหตุผล QS.get()ที่ดี 2. รายละเอียดสำคัญ: "คาดหวังเพียงหนึ่งเดียว" หมายถึงวัตถุ 0-1 เสมอหรือเป็นไปได้ที่จะมีวัตถุ 2+ รายการและควรจัดการกรณีดังกล่าวด้วย (ในกรณีlen(objs)นี้เป็นความคิดที่แย่มาก) หรือไม่? 3. อย่าคิดอะไรเกี่ยวกับค่าใช้จ่ายโดยไม่มีเกณฑ์มาตรฐาน (ฉันคิดว่าในกรณีนี้try/exceptจะเร็วขึ้นตราบเท่าที่อย่างน้อยครึ่งหนึ่งของการโทรกลับบางสิ่งบางอย่าง)
imposeren

> กำลังพยายามปรับรหัสให้เหมาะสมก่อนที่จะถูกเขียนและทำโปรไฟล์นี่เป็นคำพูดที่น่าสนใจ ฉันคิดเสมอว่าฉันควรคิดถึงวิธีที่เป็นทางเลือกมากที่สุดในการใช้บางสิ่งก่อนนำไปใช้ มันผิดหรือเปล่า? คุณสามารถอธิบายรายละเอียดเกี่ยวกับประเด็นนี้ได้ไหม? มีทรัพยากรที่อธิบายรายละเอียดนี้ไหม?
Parth Sharma

ฉันประหลาดใจที่ไม่มีใครพูดถึงก่อน () คำแนะนำอื่น ๆ ดูเหมือนจะระบุว่าเป็นการโทรสำหรับสถานการณ์นี้ stackoverflow.com/questions/5123839/…
NeilG

29

คุณสามารถติดตั้งโมดูลที่ชื่อว่าdjango-รำคาญและจากนั้นทำสิ่งนี้:

from annoying.functions import get_object_or_None

obj = get_object_or_None(MyModel, id=1)

if not obj:
    #omg the object was not found do some error stuff

1
ทำไมมันจึงน่ารำคาญที่มีวิธีเช่นนี้? ดูดีสำหรับฉัน!
โทมัส

17

1 ถูกต้อง ใน Python ข้อยกเว้นมีค่าใช้จ่ายเท่ากับการส่งคืน สำหรับหลักฐานง่ายคุณสามารถดูที่นี้

2 นี่คือสิ่งที่ Django กำลังทำอยู่ในแบ็กเอนด์ getเรียกfilterและเพิ่มข้อยกเว้นหากไม่พบรายการหรือหากพบวัตถุมากกว่าหนึ่งรายการ


1
การทดสอบนั้นค่อนข้างไม่ยุติธรรม ส่วนใหญ่ของค่าใช้จ่ายในการขว้างปาข้อยกเว้นคือการจัดการของกองติดตาม การทดสอบนั้นมีความยาวสแต็ค 1 ซึ่งต่ำกว่าปกติมากที่คุณจะพบในแอปพลิเคชัน
Rob Young

@Rob Young: คุณหมายถึงอะไร? คุณเห็นการจัดการร่องรอยสแต็คในแบบ "ขอการยกโทษมากกว่าการอนุญาต" โดยทั่วไปที่ไหน เวลาในการประมวลผลขึ้นอยู่กับระยะทางที่ข้อยกเว้นเดินทางไม่ใช่ความลึกที่เกิดขึ้นทั้งหมด (เมื่อเราไม่ได้เขียนด้วยภาษาจาวาและเรียก e.printStackTrace ()) และส่วนใหญ่มัก (ชอบในการค้นหาพจนานุกรม) - tryยกเว้นจะโยนเพียงด้านล่าง
Tomasz Gandor

12

ฉันมาช้าไปงานปาร์ตี้ แต่ด้วย Django 1.6 มีfirst()วิธีในการสืบค้น

https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.first


ส่งคืนวัตถุแรกที่จับคู่โดยชุดแบบสอบถามหรือไม่มีถ้าไม่มีวัตถุที่ตรงกัน ถ้า QuerySet ไม่มีการกำหนดลำดับแล้วชุดแบบสอบถามจะถูกจัดเรียงโดยอัตโนมัติโดยคีย์หลัก

ตัวอย่าง:

p = Article.objects.order_by('title', 'pub_date').first()
Note that first() is a convenience method, the following code sample is equivalent to the above example:

try:
    p = Article.objects.order_by('title', 'pub_date')[0]
except IndexError:
    p = None

ไม่รับประกันว่าคุณมีวัตถุเพียงรายการเดียวในแบบสอบถาม
py_dude

8

ฉันไม่สามารถพูดคุยกับประสบการณ์ของ Django ได้ แต่ตัวเลือก # 1 บอกระบบอย่างชัดเจนว่าคุณกำลังขอวัตถุ 1 ชิ้นในขณะที่ตัวเลือกที่สองไม่ได้ ซึ่งหมายความว่าตัวเลือก # 1 สามารถใช้ประโยชน์จากแคชหรือดัชนีฐานข้อมูลได้ง่ายขึ้นโดยเฉพาะอย่างยิ่งเมื่อแอตทริบิวต์ที่คุณกรองไม่ได้รับประกันว่าจะไม่ซ้ำกัน

นอกจากนี้ (อีกครั้งการเก็งกำไร) ตัวเลือกที่สองอาจต้องสร้างการรวบรวมผลลัพธ์บางอย่างหรือวัตถุตัววนซ้ำเนื่องจากการเรียกตัวกรอง () อาจส่งคืนแถวจำนวนมากได้ คุณจะข้ามสิ่งนี้ด้วยรับ ()

ในที่สุดตัวเลือกแรกนั้นทั้งสั้นลงและละเว้นตัวแปรชั่วคราวพิเศษ - มีเพียงความแตกต่างเล็กน้อย แต่ช่วยได้เล็กน้อย


ไม่มีประสบการณ์กับ Django แต่ยังคงจุดสำคัญ ความชัดเจนสั้น ๆ และปลอดภัยเป็นค่าเริ่มต้นเป็นหลักการที่ดีไม่ว่าภาษาหรือกรอบงานจะเป็นอย่างไร
nevelis

8

เหตุใดจึงทำงานทั้งหมด แทนที่ 4 บรรทัดด้วย 1 ทางลัดในตัว (นี่จะลองเอง / ยกเว้น)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

1
นี่เป็นสิ่งที่ยอดเยี่ยมเมื่อมันเป็นพฤติกรรมที่ต้องการ แต่บางครั้งคุณอาจต้องการสร้างวัตถุที่หายไปหรือดึงเป็นข้อมูลเพิ่มเติม
SingleNegationElimination

2
นั่นคือสิ่งที่Model.objects.get_or_create()มีไว้สำหรับ
boatcoder

7

ข้อมูลเพิ่มเติมบางอย่างเกี่ยวกับข้อยกเว้น หากพวกเขาไม่ได้รับการเลี้ยงดูพวกเขาไม่มีค่าใช้จ่ายเลย ดังนั้นหากคุณรู้ว่าคุณอาจจะได้รับผลลัพธ์ให้ใช้ข้อยกเว้นเนื่องจากการใช้นิพจน์แบบมีเงื่อนไขคุณต้องจ่ายค่าใช้จ่ายในการตรวจสอบทุกครั้งไม่ว่าจะเกิดอะไรขึ้น ในทางกลับกันพวกเขาเสียค่าใช้จ่ายมากกว่าการแสดงออกตามเงื่อนไขเมื่อมีการเพิ่มขึ้นดังนั้นหากคุณคาดว่าจะไม่ได้ผลลัพธ์ที่มีความถี่ (พูด 30% ของเวลาหากหน่วยความจำทำหน้าที่) การตรวจสอบเงื่อนไขจะเปิดออก จะถูกกว่านิดหน่อย

แต่นี่คือ ORM ของ Django และอาจเป็นการปัดเศษไปยังฐานข้อมูลหรือแม้กระทั่งผลลัพธ์ที่แคชไว้มีแนวโน้มที่จะครองคุณสมบัติด้านประสิทธิภาพดังนั้นโปรดอ่านได้ง่ายในกรณีนี้เนื่องจากคุณคาดหวังว่าจะใช้ผลลัพธ์get()เดียว


4

ฉันเคยเล่นกับปัญหานี้เล็กน้อยและพบว่าตัวเลือกที่ 2 ดำเนินการแบบสอบถาม SQL สองซึ่งสำหรับงานง่าย ๆ เช่นนี้มากเกินไป ดูคำอธิบายประกอบของฉัน:

objs = MyModel.objects.filter(id=1) # This does not execute any SQL
if len(objs) == 1: # This executes SELECT COUNT(*) FROM XXX WHERE filter
    obj = objs[0]  # This executes SELECT x, y, z, .. FROM XXX WHERE filter
else: 
    # we have no object!  do something
    pass

รุ่นเทียบเท่าที่ดำเนินการค้นหาเดียวคือ:

items = [item for item in MyModel.objects.filter(id=1)] # executes SELECT x, y, z FROM XXX WHERE filter
count = len(items) # Does not execute any query, items is a standard list.
if count == 0:
   return None
return items[0]

ด้วยการสลับไปใช้วิธีนี้ทำให้ฉันสามารถลดจำนวนข้อความค้นหาที่แอปพลิเคชันของฉันดำเนินการได้อย่างมาก


1

คำถามที่น่าสนใจ แต่สำหรับฉันตัวเลือก # 2 reeks ของการเพิ่มประสิทธิภาพก่อนวัยอันควร ฉันไม่แน่ใจว่าตัวละครตัวไหนที่เก่งกว่ากัน แต่ตัวเลือกอันดับ 1 แน่นอนว่าฉันรู้สึกและรู้สึกได้ถึงความไพเราะมากกว่า


1

ฉันขอแนะนำการออกแบบที่แตกต่าง

หากคุณต้องการใช้งานฟังก์ชั่นกับผลลัพธ์ที่เป็นไปได้คุณสามารถหาได้จาก QuerySet เช่นนี้: http://djangosnippets.org/snippets/734/

ผลลัพธ์ที่ได้นั้นยอดเยี่ยมมากคุณสามารถ:

MyModel.objects.filter(id=1).yourFunction()

ในที่นี้ตัวกรองจะส่งคืนชุดข้อความค้นหาที่ว่างเปล่าหรือชุดแบบสอบถามด้วยรายการเดียว ฟังก์ชันชุดแบบสอบถามที่กำหนดเองของคุณนั้นสามารถใช้งานได้และสามารถใช้ซ้ำได้ ถ้าคุณต้องการที่จะดำเนินการมันสำหรับรายการทั้งหมดของคุณ:MyModel.objects.all().yourFunction()หากคุณต้องการที่จะดำเนินการได้สำหรับรายการทั้งหมดของคุณ:

นอกจากนี้ยังเหมาะอย่างยิ่งที่จะใช้เป็นการกระทำในส่วนต่อประสานผู้ดูแลระบบ:

def yourAction(self, request, queryset):
    queryset.yourFunction()

0

ตัวเลือกที่ 1 มีความหรูหรามากกว่า แต่ให้ลองใช้.. ยกเว้น

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


0

ขออภัยที่ต้องเพิ่มอีกหนึ่งเรื่องในเรื่องนี้ แต่ฉันใช้ตัวบ่งชี้ django และในแอปผู้ดูแลระบบข้อมูลของฉันผู้ใช้จะได้รับอนุญาตให้เลือกสิ่งที่ต้องการสอบถาม บางครั้งนั่นคือ id ของเอกสาร แต่ไม่เช่นนั้นก็เป็นคิวรีทั่วไปที่ส่งคืนวัตถุมากกว่าหนึ่งวัตถุเช่น Queryset

หากผู้ใช้สอบถามรหัสฉันสามารถเรียกใช้:

Record.objects.get(pk=id)

ซึ่งส่งข้อผิดพลาดใน paginator ของ django เนื่องจากเป็น Record และไม่ใช่ Queryset of Records

ฉันต้องวิ่ง:

Record.objects.filter(pk=id)

ซึ่งส่งกลับ Queryset ที่มีหนึ่งรายการในนั้น จากนั้นผู้จัดงานก็ทำงานได้ดี


เมื่อต้องการใช้ paginator - หรือฟังก์ชันการทำงานใด ๆ ที่คาดว่าจะเป็น QuerySet - แบบสอบถามของคุณจะต้องส่งคืน QuerySet อย่าสลับระหว่างการใช้. ฟิลเตอร์ () และ. เก็ต () ติดกับ. ฟิลเตอร์ () และใส่ตัวกรอง "pk = id" ตามที่คุณได้รับรู้แล้ว นั่นคือรูปแบบสำหรับกรณีการใช้งานนี้
Cornel Masson

0

รับการตอบสนอง ()

ส่งคืนวัตถุที่ตรงกับพารามิเตอร์การค้นหาที่กำหนดซึ่งควรอยู่ในรูปแบบที่อธิบายไว้ในการค้นหาภาคสนาม

รับ () เพิ่ม MultipleObjectsReturned ถ้าพบวัตถุมากกว่าหนึ่ง กระบวนการ MultipleObjectsReturned เป็นคุณสมบัติของคลาสโมเดล

รับ () ยกข้อยกเว้น DoesNotExist หากวัตถุไม่พบสำหรับพารามิเตอร์ที่กำหนด ข้อยกเว้นนี้ยังเป็นคุณลักษณะของคลาสโมเดล

.กรอง()

ส่งคืน QuerySet ใหม่ที่มีวัตถุที่ตรงกับพารามิเตอร์การค้นหาที่กำหนด

บันทึก

ใช้ get () เมื่อคุณต้องการรับวัตถุที่ไม่ซ้ำกันหนึ่งตัวและตัวกรอง () เมื่อคุณต้องการรับวัตถุทั้งหมดที่ตรงกับพารามิเตอร์การค้นหาของคุณ

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