通常在Django框架中写view是这样的:

python
def book1(request):
    if request.method == "GET":
        return HttpResponse("GET...")

    if request.method == "POST":
        return HttpResponse("POST...")def book1(request):
    if request.method == "GET":
        return HttpResponse("GET...")

    if request.method == "POST":
        return HttpResponse("POST...")

这是传统FBV即基于函数的视图写法,定义函数,接受一个request,然后返回一个response对象,阅读过"wsgi源码","Django使用WSGI","Django的启动过程"这几篇文章的朋友就会知道:wsgi需要一个application函数对象(其实是类,没有实例化的类,就是函数名不加括号)给WSGI调用,WSGI会将environ及start_response传给这个application函数并调用,这个application在django中就是WSGIHandler类里的__call__方法。

python
 def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = "%d %s" % (response.status_code, response.reason_phrase)
        response_headers = [
            *response.items(),
            *(("Set-Cookie", c.OutputString()) for c in response.cookies.values()),
        ]
        start_response(status, response_headers)
        if getattr(response, "file_to_stream", None) is not None and environ.get(
            "wsgi.file_wrapper"
        ):
            # If `wsgi.file_wrapper` is used the WSGI server does not call
            # .close on the response, but on the file wrapper. Patch it to use
            # response.close instead which takes care of closing all files.
            response.file_to_stream.close = response.close
            response = environ["wsgi.file_wrapper"](
                response.file_to_stream, response.block_size
            )
        return response

其中有一个关键调用response = self.get_response(request) 在这个__call__中会调用get_response(request),这个request就会最终传给我们写的view函数。 在self.get_response(request)层层调用过程中,通过层层中间件校验来到最后一个中间件时,这个中间件会根据url匹配对应的view,它也只返回函数对象,最终在这里self.get_response(request)拿到这个view进行调用,并传了request。所以在定义完view后url是这样写的:

python
from django.contrib import admin
from django.urls import path

from   users import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/', views.hello, name='hello'),
    path('book1/', views.book1, name='book1')
]

在url里只要填写函数对象就行,不要调用它。wsgi只认function类(函数对象)。你给其他的会报错。基于这个原因,不管你怎么去实现这个视图,最终都是函数对象。 但是在开发中如果都是FBV,功能是没问题的,写着写着发现很多重复,于是就于类来实现,这个基于类的实现django已经写好了,在django.views 中有个View类。 示例:

python
from django.http import HttpResponse

from django.views import View


class BookView(View):
    def post(self, request):
        return HttpResponse("view post ...")

url里:
path('book2/', views.BookView.as_view(), name='book1')

在url是通过调用as_view()来注入这个函数对象,我们来看下这个as_view()那边是否会返回一个函数对象。进入View类定义:

python
class View:
    """
    Intentionally simple parent class for all views. Only implements
    dispatch-by-method and simple sanity checking.
    """

    http_method_names = [
        "get",
        "post",
        "put",
        "patch",
        "delete",
        "head",
        "options",
        "trace",
    ]

    def __init__(self, **kwargs):
        """
        Constructor. Called in the URLconf; can contain helpful extra
        keyword arguments, and other things.
        """
        # Go through keyword arguments, and either save their values to our
        # instance, or raise an error.
        for key, value in kwargs.items():
            setattr(self, key, value)

    @classproperty
    def view_is_async(cls):
        handlers = [
            getattr(cls, method)
            for method in cls.http_method_names
            if (method != "options" and hasattr(cls, method))
        ]
        if not handlers:
            return False
        is_async = iscoroutinefunction(handlers[0])
        if not all(iscoroutinefunction(h) == is_async for h in handlers[1:]):
            raise ImproperlyConfigured(
                f"{cls.__qualname__} HTTP handlers must either be all sync or all "
                "async."
            )
        return is_async

    @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(
                    "The method name %s is not accepted as a keyword argument "
                    "to %s()." % (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)
            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

        # __name__ and __qualname__ are intentionally left unchanged as
        # view_class should be used to robustly determine the name of the view
        # instead.
        view.__doc__ = cls.__doc__
        view.__module__ = cls.__module__
        view.__annotations__ = cls.dispatch.__annotations__
        # Copy possible attributes set by decorators, e.g. @csrf_exempt, from
        # the dispatch method.
        view.__dict__.update(cls.dispatch.__dict__)

        # Mark the callback if the view class is async.
        if cls.view_is_async:
            markcoroutinefunction(view)

        return view

由于我们直接BookView.as_view()直接调用as_view方法,没有传入任何值,下面代码就跳过

python
for key in initkwargs:  
    if key in cls.http_method_names:  
        raise TypeError(  
            "The method name %s is not accepted as a keyword argument "  
            "to %s()." % (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)  
        )

核心代码:

python
def view(request, *args, **kwargs):
  self = cls(**initkwargs)
  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)

分析: (1)首先是通过cls实例化一个实例,这个给了临时变量self,这个cls就是我们定义的BookView类,因为外部是BookView类调用了as_view方法。 (2)接着调用了BookView类继承的setup方法,主要是将request注入给self实例,所有能获取到的参数也注入,这一步相当于初始化,同时做了拦截,如果self实例有get属性或者方法且没有head属性或者方法,那就将get属性或者方法给head

text
def setup(self, request, \*args, \*\*kwargs):  
    """Initialize attributes shared by all view methods."""  
    if hasattr(self, "get") and not hasattr(self, "head"):  
        self.head = self.get  
    self.request = request  
    self.args = args  
    self.kwargs = kwargs

(3)调用了self的dispatch方法 这个方法 在继承的View类有定义,在as_view方法最后返回的是view函数对象,符合url要求。 return self.dispatch(request, *args, **kwargs),这里其实就是闭包了

(4)dispatch方法

python
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.    
    method =     request.method.lower()  
    if method in self.http_method_names:  
        handler = getattr(self, method, self.http_method_not_allowed)  
    else:  
        handler = self.http_method_not_allowed  
    return handler(request, *args, **kwargs)  
  
def http_method_not_allowed(self, request, *args, **kwargs):  
    response = HttpResponseNotAllowed(self._allowed_methods())  
    log_response(  
        "Method Not Allowed (%s): %s",  
        request.method,  
        request.path,  
        response=response,  
        request=request,  
    )  
  
    if self.view_is_async:  
  
        async def func():  
            return response  
  
        return func()  
    else:  
        return response

在dispatch方法里: 检验请求方法是否在self的http_method_names里(View类已经定义好),如果self实例没有这个方法,调用http_method_not_allowed方法返回response,如果self实例有这个方法,调用这个方法返回response。

接下来就是给view函数对象补齐函数对象一些方法。当外部调用view时,错误信息,包括属于哪个类等就是从下面注入的信息查询。

python
view.view_class = cls  
view.view_initkwargs = initkwargs  
  
# \_\_name\_\_ and \_\_qualname\_\_ are intentionally left unchanged as  
# view\_class should be used to robustly determine the name of the view  
# instead.  
view.__doc__ = cls.__doc__  
view.__module__ = cls.__module__  
view.__annotations__ = cls.dispatch.__annotations__  
# Copy possible atributes set by decorators, e.g. @csrf\_exempt, from  
# the dispatch method.  
view.__dict__.update(cls.dispatch.__dict__)  
  
# Mark the callback if the view class is async.  
if cls.view_is_async:  
    markcoroutinefunction(view)

所以BookView.as_view()返回的是一个函数对象,它里面会根据调用的类来实例化一个对象。表面上cls是个局部变量,其实它扔进了view函数对象里的__closure__了。每一个url里通过as_view()调用会保存一个调用类(cls),通过这个类去创建临时对象self,再通过self.dispatch方法去调用对应方法获得response。相较于FBV,as_view()多了一层创建临时实例内存消耗,但是额外消耗还是比较微末的。