使用視圖函數時,django完成URL解析以後,會直接把request對象以及URL解析器捕獲的參數(好比re_path中正則表達捕獲的位置參數或關鍵字參數)丟給視圖函數,可是在類視圖中,這些參數不能直接丟給一個類,因此就有了as_view方法,這個方法只作一件事就是返回一個閉包,這個閉包像視圖函數同樣接收url解析器傳送過來的參數。django
先擺個例子放開頭,以供參考:瀏覽器
# urls.py from blog.views import IndexView urlpatterns = [ re_path(r"^$", IndexView.as_view(), name="index"), ]
一、首先了解path和re_path的執行邏輯閉包
進到path或re_path的定義處,能夠看到他們都是partial類的實例,因此path和re_path都是對象,而不是普通函數。app
當啓動django項目時,程序執行到urlpatterns時,urlpatterns列表中的各項依次獲得執行,因爲re_path和path都是對象,當對象像函數同樣調用時,實際上是調用對象中的__call__方法,執行的結果就是,每一個path或re_path的調用都返回一個URLPattern類的實例對象(路徑爲django.urls.resolvers.URLPattern),若是打印一下re_path的執行結果,獲得以下結果:ide
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的解析,匹配到相應的回調函數,而後執行。post
這裏要提醒一點是,as_view只會執行一次,就是django在項目啓動後,以後全部請求的處理都是由as_view返回的閉包(也就是URLPattern實例對象中的回調函數)執行。url
二、再來看看上面說的閉包是什麼spa
首先給出as_view方法的完整源碼code
@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函數執行後續操做。
三、再看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方法,並返回執行結果。