Flaskのコンテキストスタックの目的は何ですか?


157

リクエスト/アプリケーションコンテキストを、それがどのように機能するのか、なぜそれがどのように設計されているのかを完全に理解せずにしばらく使用してきました。リクエストまたはアプリケーションのコンテキストに関して、「スタック」の目的は何ですか?これらの2つの別々のスタックですか、それとも両方とも1つのスタックの一部ですか?リクエストコンテキストはスタックにプッシュされますか、それともスタック自体ですか?複数のコンテキストを相互にプッシュ/ポップできますか?もしそうなら、なぜ私はそれをしたいのですか?

すべての質問で申し訳ありませんが、リクエストコンテキストとアプリケーションコンテキストのドキュメントを読んだ後も混乱します。


5
kronosapiens.github.io/blog/2014/08/14/…IMO、このブログ投稿では、フラスコのコンテキストについて最も理解しやすい説明を提供しています。
Mission.liao

回答:


242

複数のアプリ

Flaskが複数のアプリを持つことができることを理解するまで、アプリケーションコンテキスト(およびその目的)は確かに混乱します。単一のWSGI Pythonインタープリターで複数のFlaskアプリケーションを実行したい状況を想像してみてください。ここではブループリントについて話しているのではなく、まったく異なるFlaskアプリケーションについて話している。

これは、「アプリケーションディスパッチ」の例のFlaskドキュメントセクションと同様に設定できます。

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

「フロントエンド」と「バックエンド」で作成される2つのまったく異なるFlaskアプリケーションがあることに注意してください。つまり、Flask(...)アプリケーションコンストラクターが2回呼び出され、Flaskアプリケーションの2つのインスタンスが作成されています。

コンテキスト

Flaskを使用している場合、多くの場合、グローバル変数を使用してさまざまな機能にアクセスします。たとえば、おそらく次のようなコードがあります...

from flask import request

次に、ビュー中にを使用requestして、現在のリクエストの情報にアクセスできます。明らかに、requestは通常のグローバル変数ではありません。実際には、コンテキストローカル値です。つまり、「を呼び出すと、CURRENTリクエストのオブジェクトから属性をrequest.path取得する」という舞台裏の魔法がありpathますrequest。2つの異なる要求では、の結果が異なりますrequest.path

実際、Flaskを複数のスレッドで実行する場合でも、Flaskはリクエストオブジェクトを分離しておくのに十分なほどスマートです。そうすることで、それぞれが異なる要求を処理する2つのスレッドが同時に呼び出しrequest.path、それぞれの要求の正しい情報を取得することが可能になります。

それを一緒に入れて

したがって、Flaskが同じインタープリターで複数のアプリケーションを処理できること、およびFlaskが「コンテキストローカル」グローバルを使用できるようにする方法のために、「現在の」リクエストが何であるかを決定するためのメカニズムが必要であることがすでにわかりました(などrequest.path)を行うために。

これらのアイデアをまとめると、Flaskは「現在の」アプリケーションが何であるかを判別する何らかの方法を備えている必要があることも理にかなっているはずです。

おそらく、次のようなコードもあるでしょう。

from flask import url_for

私たちのrequest例のように、url_for関数には現在の環境に依存するロジックがあります。ただし、この場合、ロジックが「現在の」アプリと見なされるアプリに大きく依存していることがわかります。上記のフロントエンド/バックエンドの例では、「フロントエンド」アプリと「バックエンド」アプリの両方に「/ login」ルートをurl_for('/login')含めることができるため、ビューがフロントエンドアプリまたはバックエンドアプリのリクエストを処理しているかどうかに応じて、異なるものを返す必要があります。

あなたの質問に答えるために...

リクエストまたはアプリケーションのコンテキストに関して、「スタック」の目的は何ですか?

リクエストコンテキストドキュメントから:

リクエストコンテキストはスタックとして内部的に維持されるため、複数回プッシュおよびポップできます。これは、内部リダイレクトなどの実装に非常に便利です。

つまり、これらの「現在の」リクエストまたは「現在の」アプリケーションのスタックに通常0または1のアイテムがある場合でも、さらに多くのアイテムが存在する可能性があります。

与えられた例は、リクエストが「内部リダイレクト」の結果を返す場所です。ユーザーがAをリクエストしたが、ユーザーBに戻りたいとします。ほとんどの場合、ユーザーにリダイレクトを発行し、ユーザーにリソースBをポイントします。つまり、ユーザーはBをフェッチするために2番目のリクエストを実行します。Aこれを処理するわずかに異なる方法は、内部リダイレクトを行うことです。つまり、Aの処理中に、Flaskは自身にリソースBの新しいリクエストを作成し、この2番目のリクエストの結果をユーザーの元のリクエストの結果として使用します。

これらの2つの別々のスタックですか、それとも両方とも1つのスタックの一部ですか?

それらは2つの別個のスタックです。ただし、これは実装の詳細です。さらに重要なのは、スタックがあることではなく、いつでも「現在の」アプリまたはリクエスト(スタックの最上位)を取得できるということです。

リクエストコンテキストはスタックにプッシュされますか、それともスタック自体ですか?

「リクエストコンテキスト」は、「リクエストコンテキストスタック」の1つの項目です。「アプリコンテキスト」と「アプリコンテキストスタック」も同様です。

複数のコンテキストを相互にプッシュ/ポップできますか?もしそうなら、なぜ私はそれをしたいのですか?

Flaskアプリケーションでは、通常これを行いません。必要な場合の1つの例は、内部リダイレクト(上記)です。ただし、その場合でも、おそらくFlaskで新しいリクエストを処理することになるため、Flaskはすべてのプッシュ/ポップを実行します。

ただし、スタックを自分で操作したい場合があります。

リクエスト外でコードを実行する

人々が抱える1つの典型的な問題は、Flask-SQLAlchemy拡張機能を使用して、以下に示すようなコードを使用してSQLデータベースとモデル定義をセットアップすることです...

app = Flask(__name__)
db = SQLAlchemy() # Initialize the Flask-SQLAlchemy extension object
db.init_app(app)

次に、シェルから実行する必要があるスクリプトでappおよびdb値を使用します。たとえば、「setup_tables.py」スクリプト...

from myapp import app, db

# Set up models
db.create_all()

この場合、Flask-SQLAlchemy拡張機能はappアプリケーションを認識していますが、その間create_all()、アプリケーションコンテキストが存在しないというエラーメッセージがスローされます。このエラーは正当化されます。実行時にどのアプリケーションを処理する必要があるかをFlaskに伝えたことがないcreate_allメソッドの。

あなたはなぜこれを必要としないのか疑問に思うかもしれません with app.app_context()ビューで同様の関数を実行するときに、呼び出し。その理由は、Flaskが実際のWebリクエストを処理するときに、アプリケーションコンテキストの管理をすでに処理しているためです。問題は、モデルを1回限りのスクリプトで使用する場合など、これらのビュー関数(または他のそのようなコールバック)の外でのみ発生します。

解決策は、アプリケーションコンテキストを自分でプッシュすることです。これは、次の方法で実行できます...

from myapp import app, db

# Set up models
with app.app_context():
    db.create_all()

これにより、新しいアプリケーションコンテキストがプッシュされます( app存在する可能性があることを覚えておいてください)。

テスト中

スタックを操作したいもう1つのケースはテスト用です。リクエストを処理する単体テストを作成して、結果を確認できます。

import unittest
from flask import request

class MyTest(unittest.TestCase):
    def test_thing(self):
        with app.test_request_context('/?next=http://example.com/') as ctx:
            # You can now view attributes on request context stack by using `request`.

        # Now the request context stack is empty

3
これはまだ私を混乱させます!内部リダイレクトを行う場合は、単一のリクエストコンテキストを用意して、それを置き換えてください。私には明確なデザインのようです。
Maarten、2015

@MaartenリクエストAの処理中にリクエストBを作成し、リクエストBがスタック上のリクエストAを置き換える場合、リクエストAの処理は完了できません。ただし、提案したように置換戦略を実行し、スタックがなかった場合(つまり、内部リダイレクトがより困難になる)であっても、要求の処理を分離するためにアプリと要求のコンテキストが必要であるという事実は変わりません。
Mark Hildreth、

いい説明だ!「アプリケーションコンテキストは必要に応じて作成および破棄されます。スレッド間で移動することはなく、リクエスト間で共有されることもありません。」フラスコのドキュメント。「アプリケーションコンテキスト」がアプリとともに保持されないのはなぜですか?
jayven 2015年

1
Flaskの内部リダイレクトの例は役に立ちますが、それをググしてもあまり効果はありません。そうでなければrequest = Local()、global.py はよりシンプルなデザインで十分ではないでしょうか?おそらく私が考えていないユースケースがあるでしょう。
QuadrupleA 2016

ビューをインポートするときにアプリコンテキストをファクトリメソッド内にプッシュしても問題ありませんか?ビューにはcurrent_appを参照するルートが含まれているため、コンテキストが必要です。
変数

48

以前の回答では、リクエスト中にFlaskのバックグラウンドで何が行われているかの概要がすでに示されています。まだ読んでいない場合は、読む前に@MarkHildrethの回答をお勧めします。つまり、httpリクエストごとに新しいコンテキスト(スレッド)が作成されLocalます。そのためrequest、およびなどのオブジェクトを許可するスレッド機能が必要です。gリクエスト固有のコンテキストを維持しながら、スレッド間でグローバルにアクセスできるようにする。さらに、httpリクエストを処理している間、Flaskは内部から追加のリクエストをエミュレートできるため、それぞれのコンテキストをスタックに保存する必要があります。また、Flaskを使用すると、単一のプロセス内で複数のwsgiアプリケーションを相互に実行できます。また、リクエスト中に複数のwsgiアプリケーションを呼び出してアクションを実行できます(各リクエストにより新しいアプリケーションコンテキストが作成されます)。したがって、アプリケーションのコンテキストスタックが必要です。これは、以前の回答でカバーされていた内容の要約です。

私の目標は、今説明することで、私たちの現在の理解を補完することでどのようにフラスコとWERKZEUGが、彼らはこれらのコンテキスト地元の人々と何ありません。ロジックの理解を深めるためにコードを簡略化しましたが、これを理解すれば、実際のソース(werkzeug.localおよびflask.globals)のほとんどを簡単に理解できるはずです。

まず、Werkzeugがスレッドローカルを実装する方法を理解しましょう。

地元

HTTPリクエストが受信されると、シングルスレッドのコンテキスト内で処理されます。Werkzeugは、httpリクエスト中に新しいコンテキストを生成する代替手段として、通常のスレッドの代わりにグリーンレット(一種の軽い「マイクロスレッド」)の使用も許可します。Greenletがインストールされていない場合は、代わりにスレッドの使用に戻ります。これらのスレッド(またはグリーンレット)のそれぞれは、モジュールのget_ident()関数で取得できる一意のIDで識別できます。その関数が持つ後ろのマジックを開始点であるrequestcurrent_appurl_forg、および他のそのようなコンテキストバインドグローバルオブジェクト。

try:
    from greenlet import get_ident
except ImportError:
    from thread import get_ident

これでアイデンティティ関数ができたので、いつどのスレッドにいるかを知るLocalことができ、グローバルにアクセスできるコンテキストオブジェクトであるthreadを作成できますが、その属性にアクセスすると、それらの属性の値に解決されます。その特定のスレッド。例えば

# globally
local = Local()

# ...

# on thread 1
local.first_name = 'John'

# ...

# on thread 2
local.first_name = 'Debbie'

両方の値はグローバルにアクセス可能なLocalオブジェクトに同時に存在しますがlocal.first_name、スレッド1のコンテキスト内でアクセスするとが与えられ'John''Debbie'スレッド2で戻ります。

そんなことがあるものか?いくつかの(簡略化された)コードを見てみましょう:

class Local(object)
    def __init__(self):
        self.storage = {}

    def __getattr__(self, name):
        context_id = get_ident() # we get the current thread's or greenlet's id
        contextual_storage = self.storage.setdefault(context_id, {})
        try:
            return contextual_storage[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        context_id = get_ident()
        contextual_storage = self.storage.setdefault(context_id, {})
        contextual_storage[name] = value

    def __release_local__(self):
        context_id = get_ident()
        self.storage.pop(context_id, None)

local = Local()

上記のコードから、魔法が沸騰してget_ident()現在のグリーンレットまたはスレッドを識別することがわかります。Localストレージは、その後、ちょうど現在のスレッドに任意のデータコンテキストを保存するためのキーとしてそれを使用します。

あなたは複数持つことができLocal、プロセスごとのオブジェクトとrequestgcurrent_appおよび他の人は単にそのように作成されている可能性があります。しかし、それはFlaskで行われる方法ではなく、これらは技術的に Localオブジェクトではなく、より正確にはLocalProxyオブジェクトです。って何LocalProxy

LocalProxy

LocalProxyは、a Localにクエリを実行して、別の関心のあるオブジェクト(つまり、それがプロキシするオブジェクト)を見つけるオブジェクトです。理解してみましょう:

class LocalProxy(object):
    def __init__(self, local, name):
        # `local` here is either an actual `Local` object, that can be used
        # to find the object of interest, here identified by `name`, or it's
        # a callable that can resolve to that proxied object
        self.local = local
        # `name` is an identifier that will be passed to the local to find the
        # object of interest.
        self.name = name

    def _get_current_object(self):
        # if `self.local` is truly a `Local` it means that it implements
        # the `__release_local__()` method which, as its name implies, is
        # normally used to release the local. We simply look for it here
        # to identify which is actually a Local and which is rather just
        # a callable:
        if hasattr(self.local, '__release_local__'):
            try:
                return getattr(self.local, self.name)
            except AttributeError:
                raise RuntimeError('no object bound to %s' % self.name)

        # if self.local is not actually a Local it must be a callable that 
        # would resolve to the object of interest.
        return self.local(self.name)

    # Now for the LocalProxy to perform its intended duties i.e. proxying 
    # to an underlying object located somewhere in a Local, we turn all magic
    # methods into proxies for the same methods in the object of interest.
    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError('__dict__')

    def __repr__(self):
        try:
            return repr(self._get_current_object())
        except RuntimeError:
            return '<%s unbound>' % self.__class__.__name__

    def __bool__(self):
        try:
            return bool(self._get_current_object())
        except RuntimeError:
            return False

    # ... etc etc ... 

    def __getattr__(self, name):
        if name == '__members__':
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)

    def __setitem__(self, key, value):
        self._get_current_object()[key] = value

    def __delitem__(self, key):
        del self._get_current_object()[key]

    # ... and so on ...

    __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

    # ... and so forth ...

グローバルにアクセスできるプロキシを作成するには、次のようにします

# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')

そして今、リクエストの過程の早い段階で、以前に作成されたプロキシがアクセスできるローカル内にいくつかのオブジェクトを保存します。

# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()

自分LocalProxyで作成するのLocalsではなく、グローバルにアクセスできるオブジェクトとして使用することの利点は、管理が簡単になることです。Localグローバルにアクセス可能な多くのプロキシを作成するには、1つのオブジェクトのみが必要です。リクエストの最後に、クリーンアップ中に、1つを解放するLocal(つまり、context_idをストレージからポップする)だけで、プロキシに煩わされることはありません。プロキシは引き続きグローバルにアクセス可能でありLocal、オブジェクトを見つけるためにプロキシに委ねます後続のhttpリクエストに関心があります。

# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()

LocalProxyすでにがある場合のの作成を簡略化するためにLocal、Werkzeug Local.__call__()は次のように魔法のメソッドを実装します。

class Local(object):
    # ... 
    # ... all same stuff as before go here ...
    # ... 

    def __call__(self, name):
        return LocalProxy(self, name)

# now you can do
local = Local()
request = local('request')
g = local('g')

しかし、あなたはどのように、まだありませんフラスコソース(flask.globals)で見ればrequestgcurrent_appおよびsession作成されます。私たちが確立したように、Flaskは複数の「偽の」リクエストを(単一の真のhttpリクエストから)生成でき、その過程で複数のアプリケーションコンテキストもプッシュします。これは一般的なユースケースではありませんが、フレームワークの機能です。これらの「同時」リクエストとアプリは、いつでも「フォーカス」を持つ1つだけで実行されるように制限されているため、それぞれのコンテキストにスタックを使用することは理にかなっています。新しいリクエストが生成されるか、アプリケーションの1つが呼び出されると、それらのコンテキストがそれぞれのスタックの最上位にプッシュされます。FlaskはLocalStackこの目的でオブジェクトを使用します。彼らは彼らのビジネスを締結すると、スタックからコンテキストをポップします。

LocalStack

これはLocalStack次のようになります(ここでも、ロジックが理解しやすくなるようにコードが簡略化されています)。

class LocalStack(object):

    def __init__(self):
        self.local = Local()

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self.local, 'stack', None)
        if rv is None:
            self.local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self.local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self.local) # this simply releases the local
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self.local.stack[-1]
        except (AttributeError, IndexError):
            return None

上記から、a LocalStackはローカルに格納されたスタックであり、スタックに格納されたローカルの束ではないことに注意してください。これは、スタックはグローバルにアクセス可能ですが、スレッドごとに異なるスタックであることを意味します。

フラスコはその必要はありませんrequestcurrent_appg、とはsession直接解決オブジェクトLocalStack、それはむしろ使用するLocalProxyオブジェクトをそのラップルックアップ関数(の代わりLocalの基礎となるオブジェクトを見つけるオブジェクト)LocalStack

_request_ctx_stack = LocalStack()
def _find_request():
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return top.request
request = LocalProxy(_find_request)

def _find_session():
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return top.session
session = LocalProxy(_find_session)

_app_ctx_stack = LocalStack()
def _find_g():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.g
g = LocalProxy(_find_g)

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.app
current_app = LocalProxy(_find_app)

これらはすべてアプリケーションの起動時に宣言されますが、要求コンテキストまたはアプリケーションコンテキストがそれぞれのスタックにプッシュされるまで、実際には何にも解決されません。

コンテキストがスタックに実際に挿入される(そして次にポップアウトされる)flask.app.Flask.wsgi_app()方法を知りたい場合は、wsgiアプリのエントリポイント(つまり、Webサーバーが呼び出し、http環境をリクエストが受信されます)、RequestContextその後のpush()intoに至るまで、オブジェクトの作成に従います_request_ctx_stack。スタックの一番上にプッシュすると、からアクセスできます_request_ctx_stack.top。以下は、フローを示すためのいくつかの省略コードです。

アプリを起動して、WSGIサーバーで利用できるようにします...

app = Flask(*config, **kwconfig)

# ...

後でhttpリクエストが届き、WSGIサーバーが通常のパラメーターでアプリを呼び出します...

app(environ, start_response) # aka app.__call__(environ, start_response)

これはおおよそアプリで何が起こるかです...

def Flask(object):

    # ...

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

    def wsgi_app(self, environ, start_response):
        ctx = RequestContext(self, environ)
        ctx.push()
        try:
            # process the request here
            # raise error if any
            # return Response
        finally:
            ctx.pop()

    # ...

これは、おおよそRequestContextで何が起こるかです...

class RequestContext(object):

    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()
        self.flashes = None

    def push(self):
        _request_ctx_stack.push(self)

    def pop(self):
        _request_ctx_stack.pop()

リクエストの初期化が完了したとすると、request.pathビュー関数の1 つからのルックアップは次のようになります。

  • グローバルにアクセス可能なLocalProxyオブジェクトから開始しますrequest
  • 基になる対象オブジェクト(プロキシの対象となるオブジェクト)を見つけるには、ルックアップ関数_find_request()(関数として登録された関数)を呼び出しますself.local
  • この関数は、スタック上の最上位コンテキストのLocalStackオブジェクト_request_ctx_stackを照会します。
  • 最上位のコンテキストを見つけるために、LocalStackオブジェクトは最初にその内部Local属性(self.localstackに以前に格納されたプロパティを照会します。
  • stackそれはトップのコンテキストを取得します
  • そしてtop.requestこれ関心の基礎となるオブジェクトとして解決されます。
  • そのオブジェクトからpath属性を取得します

私たちはどのように見てきたのでLocalLocalProxy、とLocalStack仕事を、今取得中意味合いやニュアンスの瞬間のためだと思うpathから。

  • requestシンプルなグローバルアクセス可能なオブジェクトになりますオブジェクト。
  • request地元のだろうオブジェクト。
  • requestローカルの属性として保存されたオブジェクト。
  • requestローカルに格納されたオブジェクトへのプロキシであるオブジェクト。
  • requestスタックに格納されているオブジェクト、つまりローカルに格納されているオブジェクト。
  • requestローカルに格納されているスタック上のオブジェクトへのプロキシであるオブジェクト。<-これがFlaskの機能です。

4
私はフラスコ/globals.pyとwerkzeug / local.pyのコードを研究してきました。これは私の理解を明確にするのに役立ちます。私のスパイダーセンスは、これは非常に複雑な設計であることを私に教えてくれますが、それが意図されているすべてのユースケースを理解していないことは認めます。「内部リダイレクト」は上記の説明で私が見た唯一の正当化であり、「フラスコ内部リダイレクト」をグーグルすることはあまり現れないので、私はまだ少し途方に暮れています。私がフラスコについて私が好きなことの1つは、それが一般に、AbstractProviderContextBaseFactoriesなどでいっぱいのjavaオブジェクトスープ型のものではないことです。
QuadrupleA 2016

1
@QuadrupleAあなたはどのようにこれらを理解すればLocalLocalStackLocalProxy仕事、私はドキュメントのこれらの記事を再検討することをお勧め:flask.pocoo.org/docs/0.11/appcontextflask.pocoo.org/docs/0.11/extensiondev、およびflask.pocooを.org / docs / 0.11 / reqcontext。あなたの新鮮な把握はあなたにそれらを新しい光で見ることを可能にし、より多くの洞察を提供するかもしれません。
Michael Ekoka

それらのリンクを読んでください。それらはほとんど意味をなしていますが、デザインは複雑すぎて、多分それなりに利口ではないように思えます。しかし、私は一般的にOOPの大ファンではなく、暗黙のフロー制御の要素(__call __()、__ getattr __()のオーバーライド、単純な関数呼び出しに対する動的イベントディスパッチ、通常の属性を使用するだけでなくプロパティアクセサーでのラッピングなど) 。)多分それは哲学の違いにすぎないかもしれません。TDDプラクティショナーでもありません。この追加の機構の多くはサポートすることを目的としているようです。
QuadrupleA 2016

1
これを共有してくれてありがとう、ありがとう。スレッド化はpythonなどの言語の弱点です。アプリケーションフレームワークにリークする上記のようなパターンに巻き込まれ、実際にはスケーリングされません。Javaは、同様の状況におけるもう1つの例です。threadlocals、semaphorsなど。正しく取得したり、保守したりすることは非常に困難です。これは、Erlang / Elixir(BEAMを使用)などの言語、またはイベントループアプローチ(nginx対apacheなど)が通常、より強力でスケーラブルで、それほど複雑ではないアプローチを提供する場所です。
arcseldon

13

少し追加@Mark Hildrethの答え。

コンテキストスタックはのようになり{thread.get_ident(): []}、()および()操作[]のみが使用されるため、「スタック」と呼ばれます。したがって、コンテキストスタックはスレッドまたはグリーンレットスレッドの実際のデータを保持します。appendpushpop[-1]__getitem__(-1)

current_appgrequestsessionおよび等であるLocalProxyだけで、特別なメソッドをオーバーライドオブジェクト__getattr____getitem____call____eq__等、コンテキストスタックトップ(からの戻り値[-1](引数名で)current_apprequest例えば)。 LocalProxyこのオブジェクトを1回インポートする必要があり、それらは実際の動作を見逃しません。したがってrequest、コード内のどこにでもインポートするだけで、関数やメソッドにリクエスト引数を送信するのではなく、インポートするほうがよいでしょう。独自の拡張機能を簡単に作成できますが、軽薄な使用によりコードが理解しにくくなる可能性があることを忘れないでください。

時間をかけてhttps://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.pyを理解してください

それでは、両方のスタックをどのように実装しましたか?リクエストに応じてFlask

  1. request_context環境によって作成(init map_adapter、match path)
  2. このリクエストを入力またはプッシュします。
    1. 前をクリア request_context
    2. app_context失敗してアプリケーションコンテキストスタックにプッシュされた場合に作成する
    3. この要求はコンテキストスタックを要求するためにプッシュされました
    4. 失敗した場合の初期セッション
  3. 派遣依頼
  4. リクエストをクリアしてスタックからポップする

2

1つの例を考えてみましょう。(LocalおよびLocalProxyのフラスコ構造を使用して)ユーザーコンテキストを設定するとします。

1つのUserクラスを定義します。

class User(object):
    def __init__(self):
        self.userid = None

現在のスレッドまたはグリーンレット内のユーザーオブジェクトを取得する関数を定義する

def get_user(_local):
    try:
        # get user object in current thread or greenlet
        return _local.user
    except AttributeError:
        # if user object is not set in current thread ,set empty user object 
       _local.user = User()
    return _local.user

次にLocalProxyを定義します

usercontext = LocalProxy(partial(get_user, Local()))

現在のスレッドusercontext.useridでユーザーのユーザーIDを取得します

説明 :

1.LocalはIDとオブジェクトのディクテーションを持ち、IDはthreadidまたはgreenlet idです。この例では、_local.user = User()は_local .___ storage __ [current thread's id] ["user"] = User()と同等です

  1. LocalProxy 委譲 包まローカルオブジェクトへの操作か、リターンがオブジェクトをターゲットという機能を提供することができます。上記の例では、get_user関数は現在のユーザーオブジェクトをLocalProxyに提供します。usercontext.useridで現在のユーザーのユーザーIDを要求すると、LocalProxyの__getattr__関数は最初にget_userを呼び出してユーザーオブジェクト(ユーザー)を取得し、次にgetattr(user、 "userid")を呼び出します。ユーザー(現在のスレッドまたはグリーンレット)にユーザーIDを設定するには、次のようにします。usercontext.userid = "user_123"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.