15

私はDjangoの一般的なビューに飛び込み、単純なビュー関数のように、単純なHttpResponseオブジェクトを返す方法を理解しています。
テスト用の簡単なプロジェクトを作成し、ファイルdjango / views / generic / base.pyで定義されている基本的なViewクラスにいくつかのログコマンドを追加して、内部で何が起こっているかを追跡できるようにしました。

研究中に出てきた質問がいくつかあります。
私はこの投稿を短くしようと努めてきましたが、完全に理解するには、コードスニペットとログを含めることが不可欠だと感じました。
時間を割いて有益なコメントをしてくれて、おそらく私の質問のいくつかに答えてくれた人には本当に感謝しています。


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


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で呼び出される(そしてクラスをパラメーターとして受け取る) クラスメソッドです。 . 一部の言語ではこれを静的メソッドと呼んでいますが、Python では、これはここで説明する必要のない 3 番目の種類のメソッドです。cls

  2. これは、urlconf でのビューの定義方法が原因です。正しく言えば、これが行うことは、urlconf がインポートされた時点でclassmethodをWelcomeView.as_view()呼び出すことです。as_view

  3. ポイント 1 からわかるように、clsはビュー クラスそのものです。通常のクラスと同様に、呼び出すとオブジェクトが取得されます。selfつまり、あなたが言うように、ここで行っているのは、クラスをインスタンス化してから、そのインスタンスのメソッド内にいるかのように、そのインスタンスを という変数に割り当てることです。ここでのポイントは、上で述べたように、as_viewがインポート時にview呼び出され、ブラウザがその URL を要求したときに URL ディスパッチャによって呼び出される関数を返すということです。したがって、その関数内で、クラスベースのビューを構成する残りのクラスを構築して呼び出します。必要な理由については、以下を参照してください。

  4. この__init__メソッドは、 の各メンバーをインスタンス属性に設定し、通常の構文initargsを使用してビュー コードでアクセスできます。self.whatever

では、なぜこれがすべて必要なのですか?

クラスベースのビューには、URLconf 内 (またはモジュール レベルの他の場所) で直接インスタンス化されたクラスが、プロセスの存続期間全体にわたって存続するという大きな潜在的な落とし穴があります。そして、Django が一般的にデプロイされる方法 (WSGI 経由) は、通常、1 つのプロセスが多数のリクエストに対して存続できることを意味します。また、複数のリクエストにまたがって何かが持続する場合、非常に厄介なスレッド セーフ バグが発生する可能性があります。たとえば、1 つのリクエストでインスタンス属性に何かを設定すると、それが後続のリクエストで表示されます。

したがって、このコードは、各リクエストが新しいインスタンスを取得することを保証するだけでなく、ビュー関数内で毎回インスタンスを動的に構築することによって、そのリクエストの分離を破ることを非常に困難にします。

于 2013-02-24T20:53:10.793 に答える
2

1. まず as_view() はクラスメソッドです。これは、クラスのインスタンスではなく、クラスで呼び出すことができるメソッドです。Viewこの場合、インスタンスではなくクラスである で呼び出されていることがわかります。

2. as_view()url.conf モジュールがロードされると呼び出されます - 関数を返しますview()。ビューが要求されるたびに呼び出されるのはこの関数でas_viewあり、再度呼び出す必要はありません。

3.view()関数のスコープでは、cls変数はViewクラスです (例: DetailViewListView、または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 に答える