前のセクションでは、Pythonの「コンテキストマネージャー」について詳しく説明しました。今日でも、それは文脈に関連したトピックです。ここでのコンテキストは、前のセクションの内容とは少し異なるだけです。コンテキストマネージャーは、コードブロックレベルのコンテキストを管理するためのものであり、本日お話しするコンテキストは、エンジニアリングプロジェクトのコンテキストです。
たぶん、あなたはまだ文脈の概念についてはっきりしていません。ここに簡単な説明があります
多くの場合、プログラムまたは関数の操作は、実行するためにプログラム外の変数に依存する必要があります。これらの変数が分離されると、プログラムは正しく機能しなくなります。これらの外部変数は、従来の慣例に従って、これらの変数を関数パラメーターとして1つずつ渡すことです。これはアプローチであり、単純な小さなプログラムでも問題はありません。このようなアプローチを大規模なプロジェクトで使用すると、煩雑で柔軟性がなくなります。
より良いアプローチは、頻繁に使用される変数値をこれらのプロジェクトのグローバルに統合することであり、これらの値のコレクションはコンテキストです。必要に応じて、このコンテキストから取得してください。
フラスコには、2つのコンテキストがあります
- アプリケーションコンテキスト
- リクエストコンテキスト
application context
グローバルに共有できるアプリに変数を保存します。またrequest context
、リクエストが外部から開始したすべての情報を保存します。
Flaskプロジェクトには複数のアプリが存在する可能性がありますが、アプリには複数のリクエストがあります。これが両者の対応です。
上で述べたように、コンテキストは達成できますが信息共享
、同時に、1つのことが非常に重要です信息隔离
。それはです。複数のアプリが同時に実行されている場合、1つのアプリが別のアプリの変数にアクセスして変更できないようにする必要があります。これは非常に重要です。
だから正確にそれを行う方法は信息隔离
?
次に、Flaskの3つの非常に一般的なオブジェクトについて説明します。慣れていないはずです。
- 地元
- LocalProxy
- LocalStack
これらの3つのオブジェクトはwerkzeug
、local.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コアメカニズムのコンテキストを理解することが役立つでしょう。