Flask 上下文深挖:current_app、g、request、session 的底层逻辑

📂 所属阶段:第五阶段 — 高级进阶(性能与架构)
🔗 相关章节:路由(Routing)艺术 · 环境搭建


1. Flask 四大上下文

┌──────────────────────────────────────┐
│           应用上下文 (App Context)     │
│  ┌──────────────┐  ┌───────────────┐ │
│  │ current_app  │  │       g       │ │
│  │ (当前应用)   │  │ (请求级全局)  │ │
│  └──────────────┘  └───────────────┘ │
├──────────────────────────────────────┤
│           请求上下文 (Request Context) │
│  ┌──────────────┐  ┌───────────────┐ │
│  │   request    │  │    session    │ │
│  │ (当前请求)   │  │ (会话数据)    │ │
│  └──────────────┘  └───────────────┘ │
└──────────────────────────────────────┘

2. current_app(当前应用实例)

# current_app = 当前 Flask 应用实例
# 用于:在应用工厂、扩展、工具函数中访问应用配置

# ❌ 错误:无法在模块级别访问
app = create_app()  # 还没创建就访问?
print(app.config["SECRET_KEY"])

# ✅ 正确:在应用上下文中访问
def create_app():
    app = Flask(__name__)

    @app.route("/")
    def index():
        return current_app.config["APP_NAME"]

    return app

# 在扩展中使用
class MyExtension:
    def init_app(self, app):
        self.secret = app.config["SECRET_KEY"]

3. g(请求级全局对象)

# g = 每个请求独立的全局对象,请求结束后自动清除
# 用于:在同一个请求的多个函数间共享数据

from flask import g

@app.before_request
def load_user():
    """每个请求前加载用户(只查一次)"""
    g.user_id = session.get("user_id")
    g.user = User.query.get(g.user_id) if g.user_id else None

@app.route("/profile")
def profile():
    # 多个函数都能访问 g.user,不需要重复查询
    if g.user:
        return f"欢迎,{g.user.name}"
    return "未登录"

@app.route("/dashboard")
def dashboard():
    # 同样可以访问
    if g.user:
        return f"{g.user.name} 的仪表盘"
    return "请登录"

4. request(当前请求)

from flask import request

@app.route("/submit", methods=["POST"])
def submit():
    # 表单数据(application/x-www-form-urlencoded)
    username = request.form.get("username")
    password = request.form.get("password")

    # JSON 数据(application/json)
    data = request.get_json()
    name = data.get("name")

    # URL 参数(?key=value)
    page = request.args.get("page", 1, type=int)

    # 请求头
    user_agent = request.headers.get("User-Agent")
    auth = request.headers.get("Authorization")

    # 请求方法
    if request.method == "POST":
        ...

    # 上传的文件
    file = request.files.get("avatar")
    if file:
        file.save("/path/to/save")

    # 请求 URL
    print(request.url)           # 完整 URL
    print(request.endpoint)      # 视图函数名(如 "articles.detail")
    print(request.blueprint)     # 当前蓝图名

    return "OK"

5. session(会话数据)

from flask import session

# 设置会话数据
session["user_id"] = user.id
session["username"] = user.username
session.permanent = True  # 设置长期有效(需配置 PERMANENT_SESSION_LIFETIME)

# 读取会话数据
user_id = session.get("user_id")

# 删除会话数据
session.pop("user_id", None)

# 清空整个会话
session.clear()

# 加密签名 Cookie 存储(浏览器端,不可篡改)
# 存储格式:Cookie: session=eyJ1c2VyX2lkIjoyfQ==...(签名)

6. 上下文的工作原理

# 上下文 = 请求线程/协程 + 本地存储(Local)

# Flask 使用 werkzeug.local.Local 实现线程/协程安全
# 每个请求在独立的 "命名空间" 中操作

# 示例:werkzeug.local.Local 的工作方式
from werkzeug.local import LocalStack, Local

local = Local()
local.request_id = 1     # 线程 A
# 线程 B 访问 local.request_id → AttributeError(隔离!)

# Flask 自动为每个请求:
# 1. 创建 App Context
# 2. 创建 Request Context(包含 session、g)
# 3. 请求结束后清理

7. 小结

# 四大上下文速查

current_app  # 当前 Flask 应用实例(App Context)
g            # 请求级全局对象,请求结束自动清除(App Context)
request      # 当前 HTTP 请求(Request Context)
session      # 加密 Cookie 存储的会话数据(Request Context)

# 使用场景
with app.app_context():
    # 在应用外创建上下文
    with app.test_request_context():
        # 在测试中创建请求上下文
        print(current_app.name)

💡 理解上下文:上下文让 Flask 的函数和类可以在任意位置访问当前应用、请求和会话数据,无需显式传递参数。


🔗 扩展阅读