Flask の request、current_app、g、session はグローバル変数のように見えるが、実際は Werkzeug の LocalProxy という仕組みで動作している。この仕組みを理解すると、Flask のコンテキストシステムが明確になる。
なぜ LocalProxy が必要か
マルチスレッド環境で普通のグローバル変数を使うと、スレッド間でデータが混在する。しかし Flask では複数のリクエストが並行処理されても、各スレッドが正しい request を参照できる。
from flask import request
@app.route('/a')
def route_a():
return request.path # '/a'
@app.route('/b')
def route_b():
return request.path # '/b'
# 同時にアクセスしても正しいパスが返る
LocalProxy の仕組み
LocalProxy はアクセスのたびに現在のコンテキストから実際のオブジェクトを取得する。
# 簡略化したイメージ
class LocalProxy:
def __init__(self, lookup_func):
self._lookup = lookup_func
def __getattr__(self, name):
obj = self._lookup() # 毎回現在のオブジェクトを取得
return getattr(obj, name)
request は内部的に以下のような定義になっている。
from werkzeug.local import LocalProxy
def _find_request():
# 現在のリクエストコンテキストから request を取得
return _request_ctx_stack.top.request
request = LocalProxy(_find_request)
LocalStack
コンテキストはスタック構造で管理されている。
from werkzeug.local import LocalStack
_request_ctx_stack = LocalStack()
# リクエスト開始時
_request_ctx_stack.push(request_context)
# リクエスト終了時
_request_ctx_stack.pop()
スタック構造により、ネストしたコンテキストも正しく処理できる。
スレッドローカル vs Greenlet
Werkzeug の Local は threading.local() を拡張しており、Greenlet(gevent など)にも対応している。
# Werkzeug の Local は以下を自動判別
# - スレッド ID
# - Greenlet ID(gevent 使用時)
_get_current_object()
LocalProxy から実際のオブジェクトが必要な場合は _get_current_object() を使う。
# シグナルに渡す場合
from flask import current_app
real_app = current_app._get_current_object()
some_signal.send(real_app)
# isinstance チェック
from flask import Flask
isinstance(current_app._get_current_object(), Flask) # True
isinstance(current_app, Flask) # False(LocalProxy だから)
コンテキスト外でのアクセス
コンテキスト外で LocalProxy にアクセスするとエラーになる。
from flask import request
print(request.path) # RuntimeError: Working outside of request context
これは _find_request() がコンテキストスタックから何も見つけられないためである。この挙動により、コンテキストの誤用を早期に検出できる。
LocalProxy の仕組みを理解すれば、Flask のコンテキストシステムがなぜ安全にマルチスレッド動作できるのかが分かる。