?Django的QuerySets酷斃了!
在本文中我將解釋一下QuerySets是什么,它是如何工作的(如果你對它已經熟悉了,你可以直接跳到第二部分),我認為如果可以的話你應該總是返回QuerySets對象,下面讓我來談談如何做。
QuerySets很酷
QuerySet,本質上是一個給定的模型的對象列表。我說“列表”而不是“組”或更正式的“集合”因為它是有序的。事實上,你可能已經熟悉如何獲得QuerySets,因為這就是你調用variousBook.objects.XXX()方法后得到的對象。例如,考慮下面的語句:
?
Book.objects.all()
all()返回的就是Book實例的一個QuerySet,它正好包括allBookinstances,下面的其他調用你可能已經知道:
?
# Return all books published since 1990 Book.objects.filter(year_published__gt=1990) # Return all books *not* written by Richard Dawkins Book.objects.exclude(author='Richard Dawkins') # Return all books, ordered by author name, then # chronologically, with the newer ones first. Book.objects.order_by('author', '-year_published')
關于 QuerySet s最酷的是,由于這些函數操作、返回的都是一個QuerySet,你可以把他們鏈起來:
?
# Return all book published after 1990, except for # ones written by Richard Dawkins. Order them by # author name, then chronologically, with the newer # ones first. Book.objects.filter(year_published__gt=1990) \ .exclude(author='Richard Dawkins') \ .order_by('author', '-year_published')
而且這并不是全部的,它更快!
??? 在內部,一個QuerySet可以被構造、過濾、切片及像普通變量那樣在沒有實際數據庫查詢的情況下隨便傳遞,在評估處理完QuerySet前不產生數據庫活動。
所有我們確認了QuerySets很酷,不是么?
盡可能的返回QuerySets
我最近曾在一個Django應用中用一個模型來表示樹(數據結構,不是圣誕裝飾)。這意味著每一個實例在樹上都有一個指向它父節點的鏈接。它看起來像這樣:
?
class Node(models.Model): parent = models.ForeignKey(to='self', null=True, blank=True) value = models.IntegerField() def __unicode__(self): return 'Node #{}'.format(self.id) def get_ancestors(self): if self.parent is None: return [] return [self.parent] + self.parent.get_ancestors()
這工作的相當好。麻煩的是,我不得不添加另一種方法,get_larger_ancestors,它應該返回所有值大于當前節點的的父節點。這是我能實現這個:
?
def get_larger_ancestors(self): ancestors = self.get_ancestors() return [node for node in ancestors if node.value > self.value]
問題是,我基本上會在名單上審查兩次――Django一次,我自己一次。這讓我考慮到-如果get_ancestors返回QuerySet而不是列表會怎樣呢?我可以這樣做:
?
def get_larger_ancestors(self): return self.get_ancestors().filter(value__gt=self.value)
很簡單,這里更重要的是我沒有遍歷對象。我可以對get_larger_ancestors的返回使用任何我想使用的過濾器,而且感到安全――我不會得到一個未知大小的對象列表。這樣的主要優勢是我一直使用相同的查詢接口。當用戶得到了一大堆的對象,我們不知道他想怎樣對它們進行切片分塊。而返回QuerySet對象時我保證用戶知道如何處理它。
但如何實現get_ancestorsto返回一個QuerySet呢?這是一個小技巧。用一條簡單的查詢收集我們需要的數據是不可能的,使用任何預定數量的查詢也是不可能的。我們要找的法則是動態的,選擇的實現看起來很像它現在的樣子,下面就是選擇,一個更好的實現:
?
class Node(models.Model): parent = models.ForeignKey(to='self', null=True, blank=True) value = models.IntegerField() def __unicode__(self): return 'Node #{}'.format(self.id) def get_ancestors(self): if self.parent is None: return Node.objects.none() return Node.objects.filter(pk=self.parent.pk) | self.parent.get_ancestors() def get_larger_ancestors(self): return self.get_ancestors().filter(value__gt=self.value)
稍停一會,沉淀一下,馬上說出細節。
我想說的是,不論什么時候你返回一系列對象――你應該總是返回一個QuerySet替代。這樣做將允許用戶使用一種簡單、熟悉、具備更好性能的方法自由過濾、剪接和排序結果。
(從一個側面說get_ancestors查詢了數據庫,因為我使用了遞歸的self.parent。這里有一個額外的數據庫執行――當實際檢測結果時執行了這個函數,未來又執行了另外一次。當我們在數據庫查詢上使用更多的過濾器或進行高耗內存的操作時我們得到了性能的提升。這里的例子
常見的QuerySet操作
所以,執行簡單查詢時返回一個QuerySet很簡單。當我們想實現復雜一點的東西,我們需要執行相關操作(也包括一些助手函數)。下面是些小竅門(作為練習,試著理解我get_larger_ancestors的實現)。
- ??? 聯合 - QuerySet的聯合運算符是|,處理復制時管道“symbol.qs1 | qs2”返回所有來自qs1和qs2項目的QuerySet(都在QuerySet的項目將只在結果中出現一次)。
- ??? 交集 - 交集沒有特殊的操作,因為你已經知道怎么去做。 像filter等鏈接函數在原始的QuerySet和新過濾器之前起了交集的作用。
- ??? 差分 - 差分(數學上寫為qs1 \ qs2)代表所有在qs1而不在qs2中的項目。請注意,此操作是不對稱的(相對于以前的操作)。Python中恐怕沒有內置的方式,但你可以這樣做:qs1.exclude(pk__in=qs2)
- ??? 從空開始 - 開起來沒有用處但實際并非如此,正如上面例子所展示的。很多時候,當我們動態建立一個QuerySet聯合時,我們需要從一個空列表開始,這是獲取它的方法:MyModel.objects.none().
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
