R / Richie全部文章 ↑

Python · 6 分钟阅读

2. Flask 程序基本结构

目录


一个 Flask 应用从代码组织上看非常简单,但为了便于维护和测试,社区形成了
应用工厂(Application Factory)+ 蓝图(Blueprint) 的标准范式。本章从最简版
开始,逐步演进到这个现代结构。

最小可运行示例

# hello.py
from flask import Flask

app = Flask(__name__)

@app.get("/")
def index():
    return "<h1>Hello, Flask!</h1>"

if __name__ == "__main__":
    app.run(debug=True)
# 启动(任选其一)
python hello.py
flask --app hello run --debug

打开 http://127.0.0.1:5000 即可看到结果。


1. 应用实例

所有 Flask 程序都必须创建一个应用实例——它是 Flask 类的对象。

from flask import Flask
app = Flask(__name__)

Flask.__name__ 必须传入,通常传 __name__,Flask 据此确定根目录,进而定位
templates/、static/ 等相对资源。

构造函数的其它常用参数:

  • static_folder:静态资源目录,默认 static
  • static_url_path:静态资源 URL 前缀,默认 /static
  • template_folder:模板目录,默认 templates

2. 路由与视图函数

客户端发来的请求要先被映射到一个视图函数。这个映射关系叫路由。

2.1 基本路由

Flask 3.x 推荐使用方法限定的装饰器,比 @app.route("/", methods=["GET"])
更清晰:

@app.get("/")
def index():
    return "<h1>Hello, World!</h1>"

@app.post("/login")
def login():
    ...

2.2 动态路由

用尖括号声明动态段,并可通过类型转换器限制匹配:

@app.get("/user/<name>")        # 默认 string
def show_user(name: str):
    return f"<h1>Hello, {name}!</h1>"

@app.get("/post/<int:post_id>")  # int
def show_post(post_id: int):
    return f"post #{post_id}"

@app.get("/path/<path:sub>")     # path:可包含 /
def show_path(sub: str):
    return f"path = {sub}"

支持的类型转换器:

转换器 作用
string 默认值,匹配除 / 外的任意文本
int 匹配正整数
float 匹配浮点数
path 匹配含 / 的字符串
uuid 匹配 UUID 字符串

自定义转换器请参考 Werkzeug 文档:werkzeug.routing.BaseConverter。


3. 启动服务器

3.1 使用 flask run(推荐)

# 1. 显式指定应用
flask --app hello run

# 2. 命名为 app.py / wsgi.py 时可省略 --app
flask run

# 3. 常用参数
flask run --debug --host 0.0.0.0 --port 5000

生产环境不要用 flask run 启动内置服务器,应该用 gunicorn / uvicorn 等
WSGI/ASGI 服务器。

3.2 直接调用 app.run()

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=5000)

app.run() 内部其实就是 werkzeug.serving.run_simple(),仅适合开发环境。


4. 请求与响应

视图函数返回的内容就是响应。Flask 支持多种返回形式:

from flask import abort, jsonify, make_response, redirect, url_for

@app.get("/html")
def html_resp():
    return "<h1>html</h1>"                            # 1) 字符串

@app.get("/json")
def json_resp():
    return jsonify(code=0, msg="ok")                   # 2) JSON

@app.get("/status")
def status_resp():
    return "created", 201                              # 3) body + 状态码

@app.get("/headers")
def headers_resp():
    return "hi", 200, {"X-Custom": "value"}            # 4) body + 状态码 + headers

@app.get("/redirect")
def redirect_resp():
    return redirect(url_for("index"))                  # 5) 重定向

@app.get("/missing")
def missing():
    abort(404)                                         # 6) 抛出 HTTPException

状态码常用值:200(成功)、201(已创建)、301/302(重定向)、
400(请求错误)、401(未认证)、403(无权限)、404(未找到)、500(服务器错误)。


5. 请求对象

视图函数第一个参数通常是 request,封装了本次 HTTP 请求的所有信息:

from flask import request

@app.get("/ua")
def ua():
    return f"你的浏览器:{request.headers.get('User-Agent', 'unknown')}"

@app.get("/search")
def search():
    q = request.args.get("q", "")          # 查询字符串 ?q=...
    return f"搜索:{q}"

常用属性速查:

属性 / 方法 说明
request.args URL 查询参数(MultiDict)
request.form POST 表单字段
request.files 上传文件
request.json 解析后的 JSON body
request.headers 请求头(EnvironHeaders)
request.method HTTP 方法
request.path URL 路径部分
request.cookies 全部 cookie
request.remote_addr 客户端 IP

6. 上下文:让“全局对象”按请求隔离

如果每个视图函数都把 request 当参数传,会非常啰嗦。Flask 用上下文机制把
一些对象临时变成“全局”可见:

from flask import current_app, g, request, session

@app.get("/me")
def me():
    # request  / session  -> 请求上下文
    # current_app / g      -> 应用上下文
    user = session.get("user")
    return f"hi, {user}" if user else "guest"
变量名 上下文 说明
request 请求上下文 封装当前 HTTP 请求
session 请求上下文 跨请求“记住”用户状态的字典(基于签名 cookie)
current_app 应用上下文 当前激活的应用实例
g 应用上下文 单次请求内的临时存储,生命周期 = 一次请求

模板渲染、url_for()、CLI 命令等场景会自动激活上下文。在自定义线程/任务中
如需访问这些变量,需要手动 with app.app_context(): 或 with app.test_request_context():。


7. 请求钩子(Hook)

想在请求前/后做一些通用处理?用钩子:

from flask import g, request
from time import time

@app.before_request
def start_timer():
    g.start = time()

@app.after_request
def log_slow(resp):
    cost = (time() - g.start) * 1000
    if cost > 500:
        app.logger.warning("slow request: %s %s (%.1f ms)",
                           request.method, request.path, cost)
    return resp

@app.teardown_request
def cleanup(exc):
    # 无论是否异常都会执行,适合释放资源
    ...

常用钩子: before_request / after_request / teardown_request /
before_first_request 风格已被 with app.app_context(): 替代。


8. 应用工厂 + 蓝图(推荐结构)

当项目变大,把所有路由塞进一个文件会非常难维护。社区的解决方案是:

  • 应用工厂:create_app() 函数返回一个 Flask 实例,便于测试与多配置。
  • 蓝图:把一组相关路由打包成可复用的模块。

8.1 项目结构

myapp/
├── app/
│   ├── __init__.py        # create_app()
│   ├── blueprints/
│   │   ├── __init__.py
│   │   └── blog.py        # 业务蓝图
│   └── templates/
├── config.py
├── wsgi.py
└── pyproject.toml

8.2 config.py

import os

class Config:
    SECRET_KEY = os.environ.get("SECRET_KEY", "dev-only-change-me")
    DEBUG = False

class DevConfig(Config):
    DEBUG = True

class ProdConfig(Config):
    DEBUG = False

8.3 app/__init__.py(应用工厂)

from flask import Flask

def create_app(config_object="config.DevConfig") -> Flask:
    app = Flask(__name__)
    app.config.from_object(config_object)

    # 注册扩展
    # db.init_app(app)

    # 注册蓝图
    from .blueprints.blog import bp as blog_bp
    app.register_blueprint(blog_bp, url_prefix="/blog")

    return app

8.4 app/blueprints/blog.py

from flask import Blueprint, jsonify

bp = Blueprint("blog", __name__, url_prefix="/posts")

@bp.get("/")
def list_posts():
    return jsonify(posts=["post-1", "post-2"])

@bp.get("/<int:post_id>")
def get_post(post_id: int):
    return jsonify(id=post_id, title=f"post #{post_id}")

8.5 wsgi.py(程序入口)

from app import create_app

app = create_app()

if __name__ == "__main__":
    app.run(debug=True)

启动:

flask --app wsgi run --debug
# 访问:http://127.0.0.1:5000/posts/1

9. 配置管理

推荐把敏感配置放进环境变量,通过 .env 文件管理(搭配 python-dotenv):

# .env  (不要提交到 git)
FLASK_APP=wsgi:app
FLASK_DEBUG=1
SECRET_KEY=please-generate-a-random-one
DATABASE_URL=sqlite:///app.db
# config.py
from os import environ
from dotenv import load_dotenv

load_dotenv()

class Config:
    SECRET_KEY = environ["SECRET_KEY"]
    SQLALCHEMY_DATABASE_URI = environ.get("DATABASE_URL", "sqlite:///app.db")
pip install python-dotenv
flask run --debug   # 自动加载 .env

10. 完整可运行示例

# app.py —— 一切从简的单文件版
from flask import Flask, jsonify, request

app = Flask(__name__)

@app.get("/")
def index():
    return "<h1>Hello, Flask 3!</h1>"

@app.get("/hello/<name>")
def hello(name: str):
    return f"Hello, {name}!"

@app.get("/search")
def search():
    return jsonify(query=request.args.get("q", ""))

if __name__ == "__main__":
    app.run(debug=True)
python app.py
# 或
flask --app app run --debug

小结

主题 现代实践
启动 flask --app wsgi run --debug
路由 @app.get/post(...) 限定方法
响应 字符串 / JSON / (body, code, headers) / Response
应用组织 create_app() + 蓝图
配置 环境变量 + .env + python-dotenv
部署 gunicorn / uvicorn,关闭 debug

下一章 请求-响应循环与模板 会讲解 Jinja2 模板如何把数据渲染到 HTML。