2009-06-19 28 views
108

我和一些同事正在就此進行辯論。當你期待只有一個時,有沒有一種首選的方式來檢索Django中的對象?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 

第一種方法似乎更行爲上是正確的,但使用控制流異常,其可能引入一些開銷。第二個是更迂迴,但不會提出例外。

有關哪個更適合的想法?哪個更有效率?

回答

137

get()是對於這種情況特別規定。用它。

選項2幾乎正是如何在Django中實際實現get()方法,所以不應該存在「性能」差異(並且您考慮它的事實表明您違反了基本規則之一編程,即在代碼被寫入和分析之前嘗試優化代碼 - 直到你有了代碼並且可以運行代碼,你不知道它將如何執行,並且在此之前嘗試優化是一種痛苦的途徑。

1

有趣的問題,但對我的選擇#2惡臭過早的優化。我不確定哪個更高性能,但選項#1當然會讓我看起來更加焦躁不安。

6

我不能使用Django的任何經驗,但選項#1講清楚告訴你所要求的1個對象的系統,而第二個選項沒有。這意味着選項#1可以更容易地利用緩存或數據庫索引,特別是在您過濾的屬性不保證是唯一的情況下。

而且(再次,猜測),第二個選項可能會由於過濾器()調用能正常返回許多行建立某種結果集合或迭代器的對象。你可以繞過get()。

最後,第一個選項是既短,省去了額外的臨時變量 - 只有微小的差別,但每一個小小的幫助。

+0

沒有Django的經驗,但仍即期。默認情況下,明確,簡潔和安全,無論是語言還是框架,都是很好的原則。 – nevelis 2017-04-10 16:53:24

13

1是正確的。在Python中,異常與返回相同。要獲得簡化的證明,您可以查看this

2這是Django的在後端做。 get調用filter,如果找不到任何項目或找到多個對象,則會引發異常。

+1

這個測試很不公平。引發異常的大部分開銷是處理堆棧跟蹤。該測試的堆棧長度爲1,比通常在應用程序中發現的要低得多。 – 2011-05-11 16:00:44

+0

@Rob Young:你什麼意思?你在哪裏看到典型的「請求寬恕而非許可」計劃中的堆棧跟蹤處理?處理時間取決於異常行程的距離,而不是發生的深度(當我們沒有用java編寫並調用e.printStackTrace()時)。大多數情況下(比如在字典查找中) - 在`try`下面拋出異常。 – 2013-08-31 07:56:43

5

爲什麼所有這些工作?用內置快捷鍵替換4行。 (這是自己的嘗試/除了。)

from django.shortcuts import get_object_or_404 

obj = get_object_or_404(MyModel, id=1) 
+1

這是很好的,當它是所需的行爲,但有時,你可能想創建缺少的對象,或拉是可選的信息。 – SingleNegationElimination 2009-06-20 01:34:18

6

有關異常的更多信息。如果他們不提高,他們幾乎沒有成本。因此,如果你知道你可能會得到一個結果,使用這個異常,因爲使用一個條件表達式,你無論支付什麼,都要支付每次檢查的代價。另一方面,當它們被提升時,它們的成本比條件表達式要多一些,所以如果你期望不會得到某個頻率的結果(比如30%的時間,如果有內存服務的話),條件檢查結果會變成要便宜一點。

但是這是Django的ORM,可能是數據庫的往返行程,或者甚至是緩存的結果,可能會主宰性能特徵,所以在此情況下,請優先考慮可讀性,因爲您期望得到一個結果,請使用get()

27

您可以安裝一個叫做django-annoying模塊,然後做到這一點:

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 
0

選項1是更優雅,但一定要使用try..except。

根據我自己的經驗,我可以告訴你,有時候你確定數據庫中不可能有多個匹配對象,但是會有兩個......(當然除了通過它獲得對象首要的關鍵)。

1

我建議一個不同的設計。

如果你想在一個可能的結果執行功能,您可以從查詢集衍生,像這樣:http://djangosnippets.org/snippets/734/

結果是相當真棒,例如,您可以:

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

這裏,篩選器返回一個空的查詢集或一個查詢集與單個項目。您的自定義查詢集函數也是可鏈接和可重用的。如果你想爲你的所有條目執行它:MyModel.objects.all().yourFunction()

他們也是理想的用作在管理界面操作:

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

我打這個問題了一下,發現選項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] 

通過切換到這種做法,我能夠大幅降低查詢我的應用程序執行的數量。

6

我對派對有點遲到,但使用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