ソース解析PythonのFlashkフレームからrequestオブジェクトの使い方

8791 ワード

from flashk import request
Flashkは非常に人気のあるPython Webフレームです。筆者もそれを持って大小のプロジェクトを書いたことがあります。Flashkは特徴があります。私はとても好きです。つまり、どこでも現在のrequestオブジェクトを獲得したいなら、簡単なだけでいいです。
現在のrequestから内容を取得します。
  • method:開始行、メタデータ
  • host:開始行、メタデータ
  • path:開始行、メタデータ
  • environ:そのうちのSERVER_PROTOCOLは開始ラインで、メタデータ
  • headers:ヘッダ、メタデータ
  • data:body,メタデータ
  • remote_addr:クライアントアドレス
  • args:リンク中のパラメータ(GETパラメータ)を要求し、解析後
  • form:form提出中のパラメータは、解析後
  • values:argsとformsのセット
  • json:json形式のbodyデータ、解析後
  • cookies:Cookieへのリンク
  • Requestオブジェクトはパラメータの分類が細かいので、args、form、valeus、jsonの違いに注意してください。もちろん一番安全なのも一番原始的な方法は自分で解析してdataに行くことです。
    もう一つの注意すべき点は、特定の属性のタイプであり、Python標準のdictではなく、MultiDictまたはCommbinedMultiDictである。これはHTTPプロトコルでパラメータが重複することに対応するための設定です。したがって、値を取るときは、これらのオブジェクトの特性に注意してください。例えば、get()と.get_list()メソッドが戻ってくるものは違います。
    とても簡単で覚えやすいです。使ってもとても友好的です。しかし、簡単な裏に隠された実現はちょっと複雑です。私の文章に従ってその中の奥秘を見てみましょう。
    二つの疑問?
    私たちが下を見る前に、まず二つの質問をします。
    疑問の1:requestは静的なクラスの例のようにしか見えないのに、なぜ我々は直接request.argsという表現を使って現在のrequestのargs属性を取得することができますか?
    
    from flask import get_request
    
    #     request
    request = get_request()
    get_request().args
    
    
    このような方式は?flashkはどうやってrequestを現在の要求対象に対応しますか?
    疑问2:本当の生产环境では、同じ作业プロセスの下に多くのスレッドがあるかもしれません。先ほど私が言ったように、requestという例はどのようにこのような环境で正常に作业されていますか?
    その秘密を知るにはflashkのソースからしか見られません。
    ソース、ソース、またはソース
    まずflashkのソースコードを開けます。最初から_u uinit_.pyはrequestがどうやって出てくるかを見に来ました。
    
    # File: flask/__init__.py
    from .globals import current_app, g, request, session, _request_ctx_stack
    
    
    # File: flask/globals.py
    from functools import partial
    from werkzeug.local import LocalStack, LocalProxy
    
    
    def _lookup_req_object(name):
      top = _request_ctx_stack.top
      if top is None:
        raise RuntimeError('working outside of request context')
      return getattr(top, name)
    
    # context locals
    _request_ctx_stack = LocalStack()
    request = LocalProxy(partial(_lookup_req_object, 'request'))
    
    
    flashkのrequestはglobals.pyから導入されたものと見られますが、ここの定義requestのコードはrequest=LocalProxyです。lookup_req_.もしpartialが何かを知らない学生がいたら、先に授業を補ってください。まずpartialを知る必要があります。
    しかし、partialは「request」をfuncの最初のデフォルトパラメータとして使用して他のfunctionを生成するという簡単な理解ができます。
    だから、パーティーlookup_req_.object、'request'は、次のように理解できます。
    calableのfunctionを生成します。このfunctionは主に_からです。request_ctx_スタック上部の最初のRequest Conteetオブジェクトを取得し、このオブジェクトのrequest属性に戻ります。
    このwerkzugのLocalProxyは私達の注意を引きました。これは何ですか?
    
    @implements_bool
    class LocalProxy(object):
      """Acts as a proxy for a werkzeug local. Forwards all operations to
      a proxied object. The only operations not supported for forwarding
      are right handed operands and any kind of assignment.
      ... ...
    
    前の紹介を見ると、主に何をするかが分かります。名前の通り、LocarProxyは主にProxyで、werkzugのLocal対象サービスの代理です。彼は自分の操作をすべて「転送」して代理の対象にあげました。
    このProxyはPythonを通じてどのように実現されますか?答えはソースコードの中にあります。
    
    #       ,              
    
    @implements_bool
    class LocalProxy(object):
      __slots__ = ('__local', '__dict__', '__name__')
    
      def __init__(self, local, name=None):
        #             ,   __setattr__  ,self 
        # "_LocalProxy__local"        local,      
        #              ,      Python      
        # Private member,          :
        # http://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references
        #            self.__local = local      :)
        object.__setattr__(self, '_LocalProxy__local', local)
        object.__setattr__(self, '__name__', name)
    
      def _get_current_object(self):
        """
                    ,               ,     
                             ,            
          。
        """
        #                  werkzeug Local  ,     request
        #     ,        。
        if not hasattr(self.__local, '__release_local__'):
          #  LocalProxy(partial(_lookup_req_object, 'request'))  
          #     self.__local()  ,      partial(_lookup_req_object, 'request')()
          #     ``_request_ctx_stack.top.request``
          return self.__local()
        try:
          return getattr(self.__local, self.__name__)
        except AttributeError:
          raise RuntimeError('no object bound to %s' % self.__name__)
    
      #            Python      ,Local Proxy   (  )?  Python
      #       ,          operations     _get_current_object()
      #       ,           。
    
      ... ...
      __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
      __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
      __str__ = lambda x: str(x._get_current_object())
      __lt__ = lambda x, o: x._get_current_object() < o
      __le__ = lambda x, o: x._get_current_object() <= o
      __eq__ = lambda x, o: x._get_current_object() == o
      __ne__ = lambda x, o: x._get_current_object() != o
      __gt__ = lambda x, o: x._get_current_object() > o
      __ge__ = lambda x, o: x._get_current_object() >= o
      ... ...
    
    
    ここまで来て、私たちは文章の最初の二つの疑問を解くことができます。私たちはget_を使う必要がないのです。request()のような方法で現在のrequestオブジェクトを取得するには、LocalProxyの功績があります。
    Local Proxyは代理として、独自の魔法法によって作られます。私たちはrequestのすべての操作について、本物のrequestオブジェクトに向けるように代行しました。
    どうですか?request.argsはそんなに簡単ではないと分かりました。
    これから、第二の問題を見てみましょう。マルチスレッドの環境下で、requestはどのように正常に動作していますか?やはりglobals.pyに戻りましょう。
    
    from functools import partial
    from werkzeug.local import LocalStack, LocalProxy
    
    
    def _lookup_req_object(name):
      top = _request_ctx_stack.top
      if top is None:
        raise RuntimeError('working outside of request context')
      return getattr(top, name)
    
    # context locals
    _request_ctx_stack = LocalStack()
    request = LocalProxy(partial(_lookup_req_object, 'request'))
    
    
    問題の鍵はこれです。request_ctx_ロックの対象になりました。ロックスターのソースコードを見つけましょう。
    
    class LocalStack(object):
    
      def __init__(self):
        #   LocalStack           Local 
        #                  Local  
        #    Local   ,          “Stack”    ,  push、pop  
        #   ,       Local     
        self._local = Local()
    
      ... ...
    
      @property
      def top(self):
        """
                 
        """
        try:
          return self._local.stack[-1]
        except (AttributeError, IndexError):
          return None
    
    
    #   ,     _request_ctx_stack.top ,       _request_ctx_stack._local.stack[-1]
    #       Local        ,                  get_ident  
    
    #       greenlet  getcurrent  ,      flask    gevent        
    #         greenlet      ,   thread  。
    try:
      from greenlet import getcurrent as get_ident
    except ImportError:
      try:
        from thread import get_ident
      except ImportError:
        from _thread import get_ident
    
    #   ,  get_ident           /  ID,             
    
    
    class Local(object):
      __slots__ = ('__storage__', '__ident_func__')
    
      def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)
    
      ... ...
    
      #         Local    __getattr__ __setattr__       
    
      def __getattr__(self, name):
        try:
          #           self.__ident_func__(),        ID
          #    __storage__ key
          return self.__storage__[self.__ident_func__()][name]
        except KeyError:
          raise AttributeError(name)
    
      def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
          storage[ident][name] = value
        except KeyError:
          storage[ident] = {name: value}
    
      ... ...
    
      #             
    
      # Local().some_value             :
      #        get_ident            /  ID
      #       ID    some_value  ,    :
      #
      #  Local().some_value -> Local()[current_thread_id()].some_value
      #
      #              
    
    
    これらの分析によって、疑問二も解決されたと信じており、現在のスレッド/協働IDを使用して、いくつかの魔法方法を積み重ねることによって、Flashkは異なる作業スレッドに自分のステージオブジェクトを使用させることを実現しました。このようにrequestの正常な動作を保証します。
    ここまで言えば、この文章もそろそろです。使用者の利便性のために、フレームワークやツールの開発者として多くの追加的な仕事をする必要があります。時には、いくつかの言語の魔法を使うことは避けられないこともあります。
    私たちが必要とするのは、Pythonの中の魔法の部分を身につけて、魔法を使って自分のコードをもっと簡潔にして、使いやすいということです。
    魔法はまぶしくても、乱用しないようにしてください。