關於最近遇到的坑 if queryset

2022-08-18 10:33:09 字數 4104 閱讀 1004

背景

在python語法中,if obj是一種很簡潔優雅的語法糖,可以用來判斷字串是否為空,某個引數是否為none,列表是否為空。所以,在面對queryset物件時便毫不猶豫的用if queryset來做判斷,導致了效能問題。

class

pagenumberpaginator(pagenumberpagination):

.....省略無關**

def paginate_queryset(self, queryset, request, modelclass, view=none):

self.total = none if queryset else

get_total(queryset, modelclass)

return pagenumberpagination.paginate_queryset(self, queryset=queryset, request=request)

def get_paginated_response(self, data, total=none):

query_count =self.page.paginator.count

return

response(ordereddict([

('result

', 'ok'

), (

'paging

', ordereddict([

('draw

', self.draw),

('page

', self.page_param),

('page_size

', self.page_size),

('count

', query_count),

('total

', total if total else self.total or

query_count),

])),

('data

', data)

]))

現在就來具體拆分並分析為什麼不能用if queryset來判斷queryset是否為空。

技術拆解-關於if判斷

關於if判斷的問題其實就是bool型別的判斷。預設情況下

1.if會嘗試bool(obj)

2.呼叫obj.__bool__()

3.呼叫obj.__len__()

具體解析,在判斷布林型別時,if會呼叫內建的bool(obj),此函式只能返回true或false。bool(obj)的背後會先嘗試呼叫obj.__bool__()方法,存在則返回obj.__bool__()方法的結果,如果不存在,bool(obj)會嘗試呼叫x.__len__()。若返回0,則bool返回0,否則true。

所以我們可以自己擴充套件並撰寫乙個自定義滿足布林型別判斷的物件。

class

lenfunc:

def__len__

(self):

print('

len step >>>>>>>')

return 1

def__repr__

(self):

return

'lenfunc for __len__ test

'class

boolfunc:

def__bool__

(self):

print('

bool step >>>>>>>')

return

true

def__repr__

(self):

return

'boolfunc for __bool__ test

'class

mixfunc:

def__len__

(self):

print('

len step >>>>>>>')

return 1

def__bool__

(self):

print('

bool step >>>>>>>')

return

true

def__repr__

(self):

return

'mixfunc for __len__&__bool__ test

'func_list =[lenfunc(), boolfunc(), mixfunc()]

for obj in

func_list:

print

(obj)

ifobj:

print(obj, '

is true \r\n

')

執行結果如下:

特殊方法的呼叫是隱式的,一般情況下,我們是不需要關注這些內容的,除非對於我們自定義的型別進行擴充套件,想嘗試python的內建語法糖,才需要滿足協議。

另外如果是python內建的型別,比如list, str, bytearray那麼cpython會抄近路,__len__實際上會直接返回pyvarobject裡的ob_size屬性。pyvarobject是表示記憶體中長度可變的內建物件的c語言結構體。直接讀取這個值比呼叫乙個方法快多了。

技術拆解-queryset

queryset實際上是django的內建的queryset的例項。具體由來這邊就不做闡述了,有興趣的可以翻閱原始碼。下面貼上queryset的定義

相信對django熟悉的都知道queryset對資料庫的查詢結果是懶載入的。其實,真正的載入查詢資料庫資料的步驟,在__len__和__iter__都可以發現。

在__len__中會有兩步呼叫

class

queryset(object):

.....

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._iterable_class(self))

if self._prefetch_related_lookups and

notself._prefetch_done:

self._prefetch_related_objects()

第一步self._fetch_all()會查詢可迭代物件內所有結果,即進行資料庫查詢操作,賦值資料庫查詢的結果列表給當前例項queryset的_result_cache屬性,把查詢結果載入記憶體中。

第二步返回查詢資料結果列表的長度

綜上分析原因

當在分頁查詢內進行if queryset判斷時,預設先尋找__bool__方法,當前物件沒有,便會呼叫__len__方法。此時queryset作為乙個懶載入物件,本身是在分頁完聚焦在分頁的那幾條資料(10, 20 ,50, 100 whatever)進行資料庫查詢,卻因為在if呼叫了__len__方法而提前被觸發資料庫查詢。

當面臨查詢結果特別大的時候,如需要被分頁的結果有4w條,資料便會被載入在記憶體中,導致記憶體占用過高,特別是在大併發的線上環境,尤其會影響效能,造成記憶體占用,記憶體緊張,影響服務整體效能,造成卡頓,甚至服務直接down掉

建議使用queryset.exists()來替代

def

exists(self):

if self._result_cache is

none:

return self.query.has_results(using=self.db)

return bool(self._result_cache)

django版本1.11.2

如果有興趣,大家可以翻閱下原始碼,具體了解內容。

最近畢設遇到的坑

cnpm i node sass g 2 webpack後報錯 module build failed error cannot find module node sass 說明還要安裝sass到專案中,執行下面的命令 cnpm install node sass latest 3 get提交中文資...

最近遇到的一些坑

c 邏輯判斷的順序是從左向右的。conditiona conditionb 與 conditionb conditiona並不一定等價。舉個例子 void insertsort vector arr,int length arr j 1 key 由於j是int型別,所以在key 5的那次迴圈裡,j最...

關於部署php遇到的坑

業務突然要啟動乙個久不使用的php專案,發現部署到centos7上後 各種報錯 就是不行。我懷疑是apache或者php問題 就重新安裝 編譯安裝也試過就是不行。只能按笨辦法 在測試環境安裝了apache和php 還是報錯.無奈之下 切換到windows環境 發現竟然正常了.原來我的專案是從wind...