使用視圖函數時,django完成URL解析之后,會直接把request對象以及URL解析器捕獲的參數(比如re_path中正則表達捕獲的位置參數或關鍵字參數)丟給視圖函數,但是在類視圖中,這些參數不能直接丟給一個類,所以就有了as_view方法,這個方法只做一件事就是返回一個閉包,這個閉包像視圖函數一樣接收url解析器傳送過來的參數。
先擺個例子放開頭,以供參考:
# urls.py from blog.views import IndexView urlpatterns = [ re_path(r"^$", IndexView.as_view(), name="index"), ]
1、首先了解path和re_path的執行邏輯
進到path或re_path的定義處,可以看到他們都是partial類的實例,所以path和re_path都是對象,而不是普通函數。
當啟動django項目時,程序執行到urlpatterns時,urlpatterns列表中的各項依次得到執行,由於re_path和path都是對象,當對象像函數一樣調用時,其實是調用對象中的__call__方法,執行的結果就是,每個path或re_path的調用都返回一個URLPattern類的實例對象(路徑為django.urls.resolvers.URLPattern),如果打印一下re_path的執行結果,得到如下結果:
print(re_path(r"^$", IndexView.as_view(), name="index")) 執行結果: >> <URLPattern '^$' [name='index']> # 返回一個URLPattern對象
來看看URLPattern類的定義:
可以看到,URLPattern類__init__方法中的各個參數基本上就對應了傳入path或re_path中的參數,其中有個callback屬性,就是保存了回調函數的引用。而在path或re_path執行的時候,第二個參數傳入的是as_view()(注意傳入的不是as_view,而是as_view(),as_view()會立即執行),as_view()執行完成后,返回一個閉包,所以,callback保存的是這個閉包的引用。每次當請求來臨時,url解析器完成url的解析,匹配到相應的回調函數,然后執行。
這里要提醒一點是,as_view只會執行一次,就是django在項目啟動后,之后所有請求的處理都是由as_view返回的閉包(也就是URLPattern實例對象中的回調函數)執行。
2、再來看看上面說的閉包是什么
首先給出as_view方法的完整源碼
@classonlymethod def as_view(cls, **initkwargs):"""Main entry point for a request-response process.""" for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) # 實例化一個類視圖對象 if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.setup(request, *args, **kwargs) # 初始化實例屬性,保存外面傳來的參數 if not hasattr(self, 'request'): raise AttributeError( "%s instance has no 'request' attribute. Did you override " "setup() and forget to call super()?" % cls.__name__ ) return self.dispatch(request, *args, **kwargs) view.view_class = cls view.view_initkwargs = initkwargs # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view
上面的代碼有點多,但是有很多代碼對理解as_view核心作用來說是無關緊要的,下面把代碼提煉一下:
@classonlymethod def as_view(cls, **initkwargs): def view(request, *args, **kwargs): self = cls(**initkwargs) # 實例化一個類視圖對象,cls指的就是我們自定義的類視圖,比如開頭例子中的IndexView,所以self指的就是IndexView的一個實例 if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.setup(request, *args, **kwargs) # 初始化實例屬性 if not hasattr(self, 'request'): raise AttributeError( "%s instance has no 'request' attribute. Did you override " "setup() and forget to call super()?" % cls.__name__ ) return self.dispatch(request, *args, **kwargs) return view # 這就是上面說的閉包
可以看到as_view的定義中又定義了一個view函數,該函數接收三個參數,第一個是request對象,第二個是url解析器捕獲的url中的位置參數,第三個是url解析器捕獲的url中的關鍵字參數。返回的view函數就是上面所說的閉包。
先不看view函數內部的執行邏輯,而只關注django接收到請求后的處理邏輯。當django項目啟動,調用path或re_path返回URLPattern實例對象,同時as_view函數得到執行,並返回view函數的引用,傳遞給URLPattern實例對象的callback屬性,此時as_view方法的使命完成,之后每次當django接受到瀏覽器發來的請求,url解析器解析url后,將request對象和url中捕獲的參數傳遞給匹配到的回調函數(即view函數),由view函數執行后續操作。
3、再看view函數內部執行邏輯
def view(request, *args, **kwargs): self = cls(**initkwargs) # 實例化一個類視圖對象,cls指的是我們自定義的類視圖,比如開頭例子中的IndexView,所以self指的就是IndexView的一個實例 if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.setup(request, *args, **kwargs) # 初始化實例屬性 if not hasattr(self, 'request'): raise AttributeError( "%s instance has no 'request' attribute. Did you override " "setup() and forget to call super()?" % cls.__name__ ) return self.dispatch(request, *args, **kwargs)
view函數主要做了兩件事情,一是實例化一個類視圖對象,這個容易理解,是哪個類視圖對象接收了請求那就實例化哪個。二是調用dispatch方法,根據http請求方法(比如get,post)分派處理函數,dispatch方法邏輯比較簡單,但是卻是理解類視圖執行邏輯的關鍵點。先看下源碼:
def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. if request.method.lower() in self.http_method_names: # 把http方法改為小寫,並判斷該方法是否是合法的http方法 handler = getattr(self, request.method.lower(), self.http_method_not_allowed) # 在類視圖中找到對應的處理方法,返回該方法的引用給handler else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) # 執行相應的方法
很簡單了,先把http方法改為小寫,然后判斷該方法是否在http_method_names列表中(該列表保存了所有合法的http方法的小寫名稱),如果判斷請求方法是合法的,就從我們自定義的類視圖對象中獲取到該方法,將引用傳給handler,然后返回該方法執行的結果。舉例:瀏覽器發送來一個get請求,get存在於http_method_names列表中,所以是個合法的http方法,此時通過getattr獲取到自定義類視圖中的get方法,並將get方法的引用傳給handler(所以我們需要在自定義類視圖中定義get方法,否則dispatch找不到get方法,比如開頭的例子中,我們需要在IndexView類中定義get方法),最后執行get方法,並返回執行結果。