CBV的请求流程

2020-07-05  本文已影响0人  小吉头

什么是CBV

FBV(function base view)基于函数的视图
CBV(class base view)基于类的视图

#urls.py
from django.conf.urls import url
from user.views import Index,IndexView

urlpatterns = [
    url(r'^fbv/', Index),
    url(r'^cbv/', IndexView.as_view()),
]
#views.py
#FBV
def Index(request):
    if request.method == "GET":
        return HttpResponse("success")
#CBV
class IndexView(View):
    def get(self,request):
        return HttpResponse("success")

一般都使用CBV,因为类有继承、封装、多态的特性。

CBV请求流程分析

先看下路由,url(r'^cbv/', IndexView.as_view()),主要看下as_view()这个类方法。
IndexView类中并没有定义,查看父类View中有没有。View类的定义是在from django.views import View
找到父类的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.request = request
            self.args = args
            self.kwargs = kwargs
            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

这是一个类方法,最终返回了view这个方法,所以CBV和FBV路由中最终都是指向一个方法。
注意力集中到view(request, *args, **kwargs)方法上:

def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)

因为路由中调用是url(r'^cbv/', IndexView.as_view()),最终调用了父类的as_view()方法中的view()方法,所以该view()方法里的cls指的是调用时的类,即IndexView,self就是IndexView的实例。
最终返回了self.dispatch(request, *args, **kwargs),先去IndexView类中查看,有没有dispatch()这个实例方法,发现没有,再去父类View中查看,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:
            handler = getattr(self, request.method.lower(), 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):
        logger.warning(
            'Method Not Allowed (%s): %s', request.method, request.path,
            extra={'status_code': 405, 'request': request}
        )
        return http.HttpResponseNotAllowed(self._allowed_methods())

根据方法注释可以知道作用是调用正确的方法,如果方法不存在或者方法不在允许列表都会报错。
request.method.lower(),request.method返回的是大写的请求方式,比如GET,POST。转换成小写是因为我们写的请求处理方法都是小写。
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']这是允许的方法列表。
现在来看下最关键的一句代码:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
作用是获取self这个对象的 request.method.lower()属性,如果没有该属性指定调用self.http_method_not_allowed方法处理异常
翻译后:
handler = getattr(self, "get", self.http_method_not_allowed)
上面说过self是IndexView的示例,会优先在IndexView中寻找get属性或方法,等同于self.get。打印print(type(self.get))得到<class 'method'>,说明可以获取到IndexView中的get()方法的引用。
所以handler(request, *args, **kwargs)等同于get(request, *args, **kwargs)即执行了IndexView中的get()方法。

重写父类的dispatch()方法

重写父类dispatch()方法达到类似中间件的效果,每次请求开始和结束都会打印开始和结束

class IndexView(View):

    def dispatch(self, request, *args, **kwargs):
        print("请求开始")
        ret = super(IndexView, self).dispatch(request, *args, **kwargs)
        print("请求结束")
        return ret

    def get(self,request):
        return HttpResponse("success")

重写dispatch(),里面调用的其实还是父类的方法

将dispatch()方法封装到公共类中

为了方便其他类也可以使用,进一步封装到一个公共类中

class Base:
    def dispatch(self, request, *args, **kwargs):
        print("请求开始")
        ret = super(Base, self).dispatch(request, *args, **kwargs)
        print("请求结束")
        return ret


class IndexView(Base,View):

    def get(self,request):
        return HttpResponse("success")

此时,再按照CBV的流程走一遍,先找到IndexViewas_view()方法,由于是多继承,Base类中没有,去View类中继续找,找到后发现最终调用的self.dispatch()方法。还是那句话,self指的是IndexView实例,因此根据多继承关系,从左到右,在Base类中找到dispatch()方法,开始执行...
ret = super(Base, self).dispatch(request, *args, **kwargs)
执行到这一句,要调用super(Base, self).ispatch(),此时该怎么走?
先看下面的多继承super()调用顺序,再回到这里,执行IndexView.mro()得到[<class 'user.views.IndexView'>, <class 'user.views.Base'>, <class 'django.views.generic.base.View'>, <class 'object'>]Base中的super(Base, self).dispatch(request, *args, **kwargs)会到View方法中去执行dispatch()方法。

多继承中super()的调用顺序

参考这篇博客https://blog.csdn.net/u011318077/article/details/89677240
,复制了里面三个案例代码:

demo1

class Parent(object):
    def __init__(self, name):
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

# 新建属性age,继承属性name
class Son1(Parent):
    def __init__(self, name, age):
        print('Son1的init开始被调用')
        self.age = age
        # 继承父类中的__init__初始化方法
        Parent.__init__(self, name)
        print('Son1的init结束被调用')

# 新建属性gender,继承属性name
class Son2(Parent):
    def __init__(self, name, gender):
        print('Son2的init开始被调用')
        self.gender = gender
        # 继承父类中的__init__初始化方法
        Parent.__init__(self, name)
        print('Son2的init结束被调用')

# 继承属性name,age,gender
class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        # 继承父类的初始化方法
        Son1.__init__(self, name, age)
        Son2.__init__(self, name, gender)
        print('Grandson的init结束被调用')

gs = Grandson('grandson', 12, '男')
>>>
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用

Parent的初始化方法会被调用两次

demo2

class Parent(object):
    def __init__(self, name, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son1(Parent):
    def __init__(self, name, age, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init开始被调用')
        self.age = age
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init结束被调用')

class Son2(Parent):
    def __init__(self, name, gender, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init开始被调用')
        self.gender = gender
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init结束被调用')

class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        # 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
        # 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
        # super(Grandson, self).__init__(name, age, gender)
        super().__init__(name, age, gender)
        print('Grandson的init结束被调用')

print(Grandson.mro())

gs = Grandson('grandson', 12, '男')
>>>
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用

多继承使用super(),每个父类都只会被调用一次。Grandson.mro()可以查看多继承中的执行顺序

demo3

class Parent(object):
    def __init__(self, name, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son1(Parent):
    def __init__(self, name, age, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init开始被调用')
        self.age = age
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init结束被调用')

class Son2(Parent):
    def __init__(self, name, gender, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init开始被调用')
        self.gender = gender
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init结束被调用')

class Grandson(Son1, Son2):
    def __init__(self, name, age, *args, **kwargs):
        print('Grandson的init开始被调用')
        # 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
        # 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
        super(Son1, self).__init__(name, age, *args, **kwargs)
        print('Grandson的init结束被调用')

print(Grandson.mro())

gs = Grandson('grandson', 12, '男')
>>>
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用

写成super(Grandson, self).init(name, age, gender),则找Grandson下一个开始执行,与demo2结果一样
写成super(Son1, self).init(name, age, *args, **kwargs), 则找Son1下一个,从Son2开始执行


多继承,经典类中搜索顺序是深度优先,新式类中是广度优先。super只在python3中才能使用,super不是直接找父类,而是根据调用者的节点位置(节点表示super(Grandson, self).__init__(name, age, gender)中的self,即Grandson对象)的广度优先顺序来的

上一篇 下一篇

猜你喜欢

热点阅读