Как работает класс django View

Я погружаюсь в общие представления Django, выясняя, как они возвращают простой объект HttpResponse, как простая функция просмотра. Я написал простой проект для тестирования, и я добавил некоторые команды ведения журнала к базовому классу View, определенному в файле django/views/generic/base.py, чтобы я мог отслеживать, что происходит под капотом.

У меня есть некоторые вопросы, которые возникли во время моих исследований.
Однако я старался сохранить этот пост коротким, но для полного понимания я счел необходимым включить фрагменты кода и журналы.
Я буду очень благодарен всем, кто найдет время, чтобы дать некоторые полезные комментарии, возможно, отвечая на некоторые из моих вопросов.


urls.py

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

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


views.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))


Джанго/вид/общий/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.
Разве что он создает новый экземпляр View с initkwargs и присваивает его экземпляру в использовании (self)?
Если да, то зачем это нужно?

4.
Как мы можем использовать initkwargs (аргументы as_view)?

Ответ 1

В основе реализации этих представлений лежит некоторый довольно продвинутый Python, поэтому, если вы относительный новичок, это не удивительно, если вы обнаружите, что некоторые из этого кода запутываются.

  • Главное, что вы должны понимать, это то, что делает декоратор @classmethod для определения as_view(). Это означает, что этот метод не является обычным методом, который вызывается экземпляром класса (и принимает экземпляр как параметр self), но метод класса, который вызывается в самом классе (и принимает класс как параметр cls). Некоторые языки ссылаются на это как на статический метод, хотя в Python есть третий тип метода, который нам не нужно входить сюда.

  • Это связано с тем, как представление определено в urlconf. Вы правильно положили WelcomeView.as_view() - то, что это делает, вызывает метод as_view classmethod во время импорта urlconf.

  • Как известно из точки 1, cls - сам класс представления. Как обычно, с классом, когда вы его вызываете, вы получаете объект. Итак, как вы говорите, то, что мы делаем здесь, создает экземпляр класса, а затем присваивает этот экземпляр переменной с именем self, как если бы мы были внутри метода этого экземпляра. Дело здесь в том, что, как я сказал выше, as_view вызывается во время импорта, и он возвращает функцию - view - которая, в свою очередь, вызывается диспетчером URL, когда браузер запрашивает этот URL. Поэтому внутри этой функции мы строим и вызываем остальную часть класса, которая составляет представление на основе классов. Что касается необходимости, см. Ниже.

  • Метод __init__ позволяет установить для каждого члена initargs атрибут экземпляра, где вы можете получить к нему доступ в коде просмотра с помощью обычного синтаксиса self.whatever.

Итак, почему все это необходимо?

Представления, основанные на классах, имеют огромный потенциальный доступ, который заключается в том, что любой класс, созданный непосредственно в URLconf (или где-либо еще на уровне модуля), будет сохраняться на протяжении всего жизненного цикла процесса. И способ, которым Django обычно развертывается - через WSGI - обычно означает, что один процесс может длиться для многих запросов. И если у вас есть что-то, сохраняющееся в нескольких запросах, у вас есть потенциал для некоторых очень неприятных ошибок в потоковой безопасности - если вы установите что-то атрибуту экземпляра в одном запросе, например, он будет виден в последующих запросах.

Таким образом, этот код не только гарантирует, что каждый запрос получает новый экземпляр, но и делает его действительно трудным разбить эту изоляцию запроса, динамически конструируя экземпляр каждый раз внутри функции просмотра.

Ответ 2

1. Во-первых as_view() - метод класса . Это метод, который можно вызвать для класса, а не для экземпляра класса. И в этом случае вы можете видеть, что это вызов View, который является классом, а не экземпляром.

2. as_view() вызывается при загрузке модуля url.conf - он возвращает функцию view(). Эта функция, которая вызывается каждый раз, когда запрашивается представление, - as_view не требуется вызывать снова.

3. В рамках функции view() переменная cls представляет собой класс View (например, DetailView, ListView или любой другой дочерний элемент View вызывает функцию). Ссылаясь на первый аргумент метода класса как cls, это спецификация стиля кодирования из PEP8. Это похоже на то, как мы ссылаемся на первый аргумент метода экземпляра am как self. Так

self = cls(**initkwargs)

в основном совпадает с

self = View(**initkwargs) или self = DetailView(**initkwargs)

(в зависимости от того, какой класс наследует эту функцию).

Это, как вы говорите, экземпляр нового экземпляра класса. До этого момента объект View еще не создан.

4. Наконец, initkwargs используются, когда создается экземпляр класса. Это действительно так же просто, как добавление каждого ключа, пары значений в качестве атрибутов нового объекта представления -

for key, value in kwargs.iteritems():
    setattr(self, key, value)