Python · 6 分钟阅读
2. Flask 程序基本结构
目录
- 最小可运行示例
- 1. 应用实例
- 2. 路由与视图函数
- 3. 启动服务器
- 4. 请求与响应
- 5. 请求对象
- 6. 上下文:让“全局对象”按请求隔离
- 7. 请求钩子(Hook)
- 8. 应用工厂 + 蓝图(推荐结构)
- 9. 配置管理
- 10. 完整可运行示例
- 小结
一个 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:静态资源目录,默认staticstatic_url_path:静态资源 URL 前缀,默认/statictemplate_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。