15

我正在深入研究 Django 的通用视图,弄清楚它们如何返回一个简单的 HttpResponse 对象,就像一个简单的视图函数一样。
我编写了一个简单的测试项目,并在 django/views/generic/base.py 文件中定义的基本视图类中添加了一些日志记录命令,这样我就可以跟踪幕后发生的事情。

我在研究过程中遇到了一些问题。
然而,我一直试图保持这篇文章的简短,为了全面理解,我认为包含代码片段和日志是必不可少的。
我将非常感谢任何花时间提供一些有用评论的人,可能会回答我的一些问题。


网址.py

from django.conf.urls import patterns, url
from views import WelcomeView

urlpatterns = patterns('',
    url(r'^welcome/(?P<name>\w+)/$', WelcomeView.as_view()),
)


视图.py

from django.http import HttpResponse
from django.views.generic import View

class WelcomeView(View):
    def get(self, request, name):
        return HttpResponse('What is up, {0}?'.format(name))


django/views/generic/base.py

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

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

    def __init__(self, **kwargs):

        #####logging
        logging.error('*** View.__init__ is started with kwargs: {0} ***'.format(
            repr(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.iteritems():
            setattr(self, key, value)

        #####logging
        logging.error('*** View.__init__ reached its end. No return value. ***')

    @classonlymethod
    def as_view(cls, **initkwargs):

        #####logging
        logging.error('*** View.as_view is started with initkwargs: {0} ***'.format(
            repr(initkwargs)))

        """
        Main entry point for a request-response process.
        """

        # sanitize keyword arguments
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError(u"You tried to pass in the %s method name as a "
                                u"keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError(u"%s() received an invalid keyword %r" % (
                    cls.__name__, key))

        def view(request, *args, **kwargs):

            #####logging
            logging.error('*** View.as_view.view is called with args: {0};\
                and kwargs: {1} ***'.format(
                repr(args),
                repr(kwargs)))

            self = cls(**initkwargs) 
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get

            #####logging
            logging.error('*** View.as_view.view reached its end.\ 
                Now calls dispatch() and returns the return value.')

            return self.dispatch(request, *args, **kwargs)

        # 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=())

        #####logging
        logging.error('*** View.as_view reached its end. Now returns view. ***')
        return view



    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.

        #####logging
        logging.error('*** View.dispatch called, with args: {0};\
            and kwargs: {1} ***'.format(
            repr(args),
            repr(kwargs)))

        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
        self.request = request
        self.args = args
        self.kwargs = kwargs

        #####logging
        logging.error('*** View.dispatch reached its end.\ 
            Now calls handler and returns the return value. ***')
        return handler(request, *args, **kwargs)

    def http_method_not_allowed(self, request, *args, **kwargs):
        allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
        logger.warning('Method Not Allowed (%s): %s', request.method, request.path,
            extra={
                'status_code': 405,
                'request': self.request
            }
        )
        return http.HttpResponseNotAllowed(allowed_methods)


一些测试请求的日志

Django version 1.4.5, using settings 'try1.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

ERROR:root:*** View.as_view is started with initkwargs: {} ***
ERROR:root:*** View.as_view reached its end. Now returns view. ***
ERROR:root:*** View.as_view.view is called with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.__init__ is started with kwargs: {} ***
ERROR:root:*** View.__init__ reached its end. No return value. ***
ERROR:root:*** View.as_view.view reached its end. Now calls dispatch() and returns the return value.
ERROR:root:*** View.dispatch called, with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.dispatch reached its end. Now calls handler and returns the return value. ***
[24/Feb/2013 12:43:19] "GET /welcome/Dude/ HTTP/1.1" 200 17

ERROR:root:*** View.as_view.view is called with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.__init__ is started with kwargs: {} ***
ERROR:root:*** View.__init__ reached its end. No return value. ***
ERROR:root:*** View.as_view.view reached its end. Now calls dispatch() and returns the return value.
ERROR:root:*** View.dispatch called, with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.dispatch reached its end. Now calls handler and returns the return value. ***
[24/Feb/2013 12:43:32] "GET /welcome/Dude/ HTTP/1.1" 200 17

[24/Feb/2013 12:44:43] "GET /welcome/ HTTP/1.1" 404 1939

ERROR:root:*** View.as_view.view is called with args: (); and kwargs: {'name': u'Bro'} ***
ERROR:root:*** View.__init__ is started with kwargs: {} ***
ERROR:root:*** View.__init__ reached its end. No return value. ***
ERROR:root:*** View.as_view.view reached its end. Now calls dispatch() and returns the return value.
ERROR:root:*** View.dispatch called, with args: (); and kwargs: {'name': u'Bro'} ***
ERROR:root:*** View.dispatch reached its end. Now calls handler and returns the return value. ***
[24/Feb/2013 12:44:59] "GET /welcome/Bro/ HTTP/1.1" 200 16


毕竟我的问题

1
、根据日志,as_view是在View.init之前调用的。
这是否意味着它甚至在创建 View 实例之前就调用了 View 方法?

2.
as_view()为什么第一次调用执行后不调用?
我还不是 Python 的导入、编译和内存使用方面的专家,
但我有一种感觉,它们在这里发挥了一些作用。

3
、在view()的定义中,下面的代码片段做了什么?

self = cls(**initkwargs)

根据日志,它触发了 View.init。
是不是它用initkwargs创建了一个新的View实例并将其分配给正在使用的实例(self)?
如果是这样,为什么需要它?

4.
我们如何利用 initkwargs(as_view 的参数)?

4

2 回答 2

23

这些视图的底层实现涉及到一些相当高级的 Python,因此,如果您是一个相对初学者,如果您发现其中一些代码令人困惑,这不足为奇。

  1. 您应该了解的主要内容是@classmethod装饰器对as_view(). 这意味着这个方法不是一个普通的方法,它是在类的实例上调用的(并以实例为self参数),而是一个类方法,它是在类本身上调用的(并以cls参数) . 一些语言将其称为静态方法,尽管在 Python 中这是我们不需要在这里介绍的第三种方法。

  2. 这是因为视图在 urlconf 中是如何定义的。你说得对WelcomeView.as_view()——这样做是在导入 urlconf 时as_view调用classmethod 。

  3. 正如我们从第 1 点所知道的,cls是视图类本身。与一个类一样,当你调用它时,你会得到一个对象。因此,正如您所说,我们在这里所做的是实例化类,然后将该实例分配给名为 的变量self,就好像我们在该实例的方法中一样。这里的重点是,正如我上面所说,as_view在导入时调用,它返回一个函数——view当浏览器请求该 URL 时,该函数又由 URL 调度程序调用。因此,在该函数内部,我们构造并调用构成基于类的视图的类的其余部分。至于为什么需要它,请参见下文。

  4. __init__方法负责将 的每个成员设置initargs为实例属性,您可以通过通常的self.whatever语法在视图代码中访问它。

那么,为什么这一切都是必要的呢?

基于类的视图有一个巨大的潜在陷阱,即直接在 URLconf(或模块级别的任何其他地方)中实例化的任何类都将在整个过程的生命周期中持续存在。而 Django 通常部署的方式——通过 WSGI——通常意味着一个进程可以持续处理许多请求。而且,如果您在多个请求中持续存在某些内容,则可能会出现一些非常讨厌的线程安全错误-例如,如果您在一个请求中为实例属性设置了某些内容,那么它将在后续请求中可见。

因此,这段代码不仅确保了每个请求都获得了一个新实例,而且还使得通过每次在视图函数中动态构造实例来打破请求隔离变得非常困难。

于 2013-02-24T20:53:10.793 回答
2

1.首先as_view()是一个类方法。这是一种可以在类而不是类的实例上调用的方法。在这种情况下,您可以看到它正在被调用View,它是一个类而不是一个实例。

2. as_view()在加载 url.conf 模块时调用 - 它返回函数view()。每次请求视图时都会调用此函数 -as_view不需要再次调用。

3. 在view()函数的范围内,cls变量是View类(例如DetailView,,或调用函数的ListView任何子类)。View引用类方法的第一个参数是PEP8cls的编码风格规范。这类似于我们将 am 实例方法的第一个参数称为 self 的方式。所以

self = cls(**initkwargs)

基本上是一样的

self = View(**initkwargs)或者self = DetailView(**initkwargs)

(取决于哪个类继承了这个函数)。

正如您所说,这是实例化该类的新实例。到目前为止,View尚未实例化对象。

4. 最后,在创建类的实例时使用 initkwargs。这真的就像将每个键值对添加为新视图对象的属性一样简单 -

for key, value in kwargs.iteritems():
    setattr(self, key, value)
于 2013-02-24T21:03:44.053 回答