ソースコードの解釈:フラスココンテキストとプロキシモード

前のセクションでは、Pythonの「コンテキストマネージャー」について詳しく説明しました。今日でも、それは文脈に関連したトピックです。ここでのコンテキストは、前のセクションの内容とは少し異なるだけです。コンテキストマネージャーは、コードブロックレベルのコンテキストを管理するためのものであり、本日お話しするコンテキストは、エンジニアリングプロジェクトのコンテキストです。

たぶん、あなたはまだ文脈の概念についてはっきりしいません。ここに簡単な説明があります

多くの場合、プログラムまたは関数の操作は、実行するためにプログラム外の変数に依存する必要があります。これらの変数が分離されると、プログラムは正しく機能しなくなります。これらの外部変数は、従来の慣例に従って、これらの変数を関数パラメーターとして1つずつ渡すことです。これはアプローチであり、単純な小さなプログラムでも問題はありません。このようなアプローチを大規模なプロジェクトで使用すると、煩雑で柔軟性がなくなります。

より良いアプローチは、頻繁に使用される変数値をこれらのプロジェクトのグローバルに統合することであり、これらの値のコレクションはコンテキストです。必要に応じて、このコンテキストから取得してください。

フラスコには、2つのコンテキストがあります

  • アプリケーションコンテキスト
  • リクエストコンテキスト

application contextグローバルに共有できるアプリに変数を保存します。またrequest context、リクエストが外部から開始したすべての情報を保存します。

Flaskプロジェクトには複数のアプリが存在する可能性がありますが、アプリには複数のリクエストがあります。これが両者の対応です。

上で述べたように、コンテキストは達成できますが信息共享、同時に、1つのことが非常に重要です信息隔离それはです。複数のアプリが同時に実行されている場合、1つのアプリが別のアプリの変数にアクセスして変更できないようにする必要があります。これは非常に重要です。

だから正確にそれを行う方法は信息隔离

次に、Flaskの3つの非常に一般的なオブジェクトについて説明します。慣れていないはずです。

  • 地元
  • LocalProxy
  • LocalStack

これらの3つのオブジェクトはwerkzeuglocal.py内部の定義であるオファーに含まれているため、Flaskに固有のものではありません。つまり、Flask環境に依存するのではなく、独自のプロジェクトで直接使用できます。

1.ローカル

まずLocal、「コンカレントプログラミングシリーズ」の前回のスレッドの第5章信息隔离、前述のようにthreading.local、目的を達成するために現在の可変スレッド、スレッド分離を格納するように設計されていることを覚えておいてください

そして、フラスコのローカルはこれと同じ機能を持っています。

このローカルの役割を理解するための最良の方法は、2つのコードを直接比較することです。

まず、Localを使用せずに、name属性(デフォルトではwangbm)を使用して新しいクラスを作成し、スレッドを開始します。次に、name属性をwuyanzuに変更します。

import time
import threading

class People:
    name = 'wangbm'

my_obj = People()

def worker():
    my_obj.name = 'wuyanzu'

new_task = threading.Thread(target=worker)
new_task.start()

# 休眠1s,保证子线程先执行
time.sleep(1)

print(my_obj.name)

実行結果が想像できます、それはwuyanzuです。子スレッドによってオブジェクトに加えられた変更は、メインスレッドに直接影響を与える可能性があります。

次に、ローカルを使用して達成します

import time
import threading

from werkzeug.local import Local

class People:
    name = 'wangbm'

my_obj = Local()
my_obj.name = 'wangbm'

def worker():
    my_obj.name = 'wuyanzu'
    print('in subprocess, my_obj.name: '+str(my_obj.name))

new_task = threading.Thread(target=worker)
new_task.start()

# 休眠1s,保证子线程先执行
time.sleep(1)

print('in mainprocess, my_obj.name: '+str(my_obj.name))

印刷結果は以下のとおりですが、サブスレッドの変更はメインスレッドに影響を与えないことがわかります。

in subprocess, my_obj.name: wuyanzu
in mainprocess, my_obj.name: wangbm

では、Localはどのようにそれを行うのでしょうか?実際、原則は非常に単純で、基本的なデータ構造である辞書を使用します。

スレッドがローカルオブジェクトの変数(変数名k1と変数値v1を含む)を変更すると、ソースコードから、現在のスレッドのIDを__storage__キーとして最初に取得し(このストレージはネストされた辞書です)、値、辞書です、{k1: v1}

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

例は次のとおりです

# 0 和 1 是线程 id
self.__storage__['0'][k1] = v1
self.__storage__['1'][k2] = v2

カプセル化のレイヤーとしてスレッドIDを使用するため、スレッドの分離を実現できます。

グラフを使用して表現する場合、最初のローカルオブジェクトは空のボックスです

異なるスレッドがデータを書き込む場合、ローカルオブジェクトは各スレッドにマイクロボックスを割り当てます。

localmanager管理する必要のあるローカルは、要求後にlocalmanager.cleanup()関数を呼び出しlocal.__release_local__ます。実際、データのクリーンアップを要求します。それを行う方法は、次のコードを見てください。

   from werkzeug.local import Local, LocalManager

   local = Local()
   local_manager = LocalManager([local])

   def application(environ, start_response):
       local.request = request = Request(environ)
       ...

   # make_middleware会确保当request结束时,所有存储于local中的对象的reference被清除
   application = local_manager.make_middleware(application)

以下はLocalコードです、必要がありますここで直接見ることができます。

class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            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}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

2.LocalStack

Localの導入により、Localが実際に辞書をカプセル化してスレッドの分離を実現していることがわかります。

次に紹介するのLocalStackは同じアイデアで、LocalStackカプセル化さLocalれているため、ローカルスレッド分離特性とスタック構造特性の両方があります。pop、push、topを介してオブジェクトにアクセスできます。

また、写真を使用して表現する

スタック構造の特徴は、ラストイン、ファーストアウトに他なりません。ここで言うまでもなく、ここでの焦点は、スレッド分離の特性を反映する方法、または上記の例を少し変更して使用する方法です。

import time
import threading

from werkzeug.local import LocalStack


my_stack = LocalStack()
my_stack.push('wangbm')

def worker():
    print('in subthread, my_stack.top is : '+str(my_stack.top) + ' before push')
    my_stack.push('wuyanzu')
    print('in subthread, my_stack.top is : ' + str(my_stack.top) + ' after push')

new_task = threading.Thread(target=worker)
new_task.start()

# 休眠1s,保证子线程先执行
time.sleep(1)

print('in main thread, my_stack.top is : '+str(my_stack.top))

出力結果は次のようになります。子スレッドのmy_stackとメインスレッドのmy_stackを共有できず、実際に分離が行われていることがわかります。

in subthread, my_stack.top is : None before push
in subthread, my_stack.top is : wuyanzu after push
in main thread, my_stack.top is : wangbm

Flaskには、2つの主要なコンテキストとがAppContextありRequestContextます。

リクエストが開始されると、Flaskは最初にスレッドを開き、次にリクエストRequestContextにはLocalStackオブジェクトの1つ_request_ctx_stackのコンテキスト情報が含まれ、プッシュする前に、実際には別のLocalStackオブジェクト(_app_ctx_stack)が空であることを検出します(ただし、通常は_app_ctx_stack空にはなりません)、空の場合は、最初_app_ctx_stackアプリのコンテキスト情報をプッシュしてから、リクエストのコンテキスト情報をプッシュします_request_ctx_stack

フラスコには3つの一般的に使用されるオブジェクトがあります

  • current_app
  • リクエスト
  • セッション

これらの3つのオブジェクトは、常にLocalStackスタック最上位のコンテキスト対応するアプリ、リクエスト、またはセッションを指します。対応するソースコードは次のとおりです。

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app

_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))

3.LocalProxy

上記のコードから、LocalStackの要素にアクセスすると、全員が通過することがわかりますLocalProxy

これは非常に奇妙です、なぜ訪問LocalしてLocalStackやってみませんか?

これは難しい点ですが、例を挙げましょう。

1つ目は、LocalProxyを使用しない場合です。

# use Local object directly
from werkzeug.local import LocalStack

user_stack = LocalStack()
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})

def get_user():
    # do something to get User object and return it
    return user_stack.pop()


# 直接调用函数获取user对象
user = get_user()
print user['name']
print user['name']

出力は

John
John

LocalProxyを使用した後

# use LocalProxy
from werkzeug.local import LocalStack, LocalProxy
user_stack = LocalStack()
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})

def get_user():
    # do something to get User object and return it
    return user_stack.pop()

# 通过LocalProxy使用user对象
user = LocalProxy(get_user)
print user['name']
print user['name']

出力結果

John
Bob

違いを確認し、LocalStackオブジェクトを直接使用します。ユーザーが割り当てられると、動的に更新できなくなります。プロキシを使用している間、オペレーターが呼び出されるたびに(ここで[]操作符は属性を取得するために使用されます)、ユーザーが再度取得されるため、動的になります。ユーザーの効果を更新します

毎回、クラスuser['name']をトリガーしてLocalProxyクラス__getitem__を呼び出し_get_current_objectます。そして、それぞれ_get_current_objectが返されますget_user()(対応する関数はフラスコ内にあります_lookup_req_object)実行結果、つまりuser_stack.pop()

def __init__(self, local, name=None):
    # 【重要】将local对象(也就是一个get_user函数对象)赋值给self.__local
    object.__setattr__(self, '_LocalProxy__local', local)
    object.__setattr__(self, '__name__', name)
    if callable(local) and not hasattr(local, '__release_local__'):
        # "local" is a callable that is not an instance of Local or
        # LocalManager: mark it as a wrapped function.
        object.__setattr__(self, '__wrapped__', local)

def _get_current_object(self):
    """Return the current object.  This is useful if you want the real
    object behind the proxy at a time for performance reasons or because
    you want to pass the object into a different context.
    """
    if not hasattr(self.__local, '__release_local__'):
        # 【重要】执行传递进行的 get_user 对象。
        return self.__local()
    try:
        return getattr(self.__local, self.__name__)
    except AttributeError:
        raise RuntimeError('no object bound to %s' % self.__name__)

このようにして、スタックの最上位の要素に対する各操作は、最新の要素に直面して実行されます。

4.古典的な間違い

Flaskでよく発生するエラーは次のとおりです。

アプリケーションコンテキスト外での作業。

フラスコのコンテキストメカニズムを理解していないと、このエラーを理解するのは困難です。上記の知識の背景に基づいて、これが発生する理由を理解することができます。

まず、このエラーの発生をシミュレートしましょう。次の内容の別のファイルがあるとします。

from flask import current_app

app = Flask(__name__)

app = current_app
print(app.config['DEBUG'])

実行すると、次のエラーが報告されます。

Traceback (most recent call last):
  File "/Users/MING/PycharmProjects/fisher/app/mytest/mytest.py", line 19, in <module>
    print(app.config['DEBUG'])
  File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/werkzeug/local.py", line 347, in __getattr__
    return getattr(self._get_current_object(), name)
  File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/werkzeug/local.py", line 306, in _get_current_object
    return self.__local()
  File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/flask/globals.py", line 51, in _find_app
    raise RuntimeError(_app_ctx_err_msg)
RuntimeError: Working outside of application context.

あなたは驚かなければなりません。明らかにアプリオブジェクトもインスタンス化しましたが、current_appを取得するとエラーが発生するのはなぜですか?また、current_appが使用されていない場合、エラーは報告されません。

上記の内容を注意深く研究すれば、ここで理解することは難しくありません。

以前の調査から、使用するとスタックの最上位要素(アプリコンテキスト情報)current_app使用されることがわかりましたLocalStack。実際、app = Flask(__name__)アプリオブジェクトインスタンス化するとき、このコンテキスト情報は現時点では書き込まれていませんLocalStack。当然です。一番上の要素を取得するとエラーが発生します。

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app

上で述べたように、このコンテキストはいつプッシュされますか?外部リクエストが開始されると、最初にアプリのコンテキスト情報がプッシュインされているかどうかがチェックされます。プッシュインされていない場合は、最初にプッシュインされます。

上記は単一のファイルを実行する方法であり、リクエストが実際に生成されるLocalStackことはありません。当然、アプリにはコンテキスト情報はありません。エラーを報告するのは正常です。

エラーの原因を知った後、この問題を解決する方法は?

方法を提供フラスコ中、ctx=app.app_context()あなたはコンテキストオブジェクトを取得することができますが、限り、私たちは、このコンテキストオブジェクトを手動でプッシュする意志ようLocalStackで、current_appオブジェクトの我々のアプリを取るために、通常のかもしれません。

from flask import Flask, current_app

app = Flask(__name__)
ctx = app.app_context()
ctx.push()

app = current_app
print(app.config['DEBUG'])
ctx.pop()

AppContextクラスがコンテキストプロトコルを実装しているため

class AppContext(object):
    def __enter__(self):
        self.push()
        return self

    def __exit__(self, exc_type, exc_value, tb):
        self.pop(exc_value)

        if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
            reraise(exc_type, exc_value, tb)

だからあなたもこのように書くことができます

from flask import Flask, current_app

app = Flask(__name__)

with app.app_context():
    app = current_app
    print(app.config['DEBUG'])

上記では、7月Flask高级编程まで、独自のわかりやすい方法で学習しました。Flaskコアメカニズムのコンテキストを理解することが役立つでしょう。

おすすめ

転載: blog.csdn.net/weixin_36338224/article/details/109325111