路由(Routing)艺术:变量规则、HTTP 方法与唯一 URL
📂 所属阶段:第一阶段 — 破冰启航(基础篇)
🔗 相关章节:初识 Flask · Jinja2 模板引擎
路由,就是把浏览器请求的 URL 路径,和我们写的 Python 处理函数一对一绑定的机制。Flask 的路由系统灵活、优雅,是它入门友好的核心原因之一。今天我们就把这个基础但核心的功能啃透。
1. 从最简单的路由开始
1.1 基础装饰器写法
Flask 用 @app.route() 装饰器来定义路由,装饰器的参数就是要绑定的 URL 路径。我们先写一段最直观的 Hello Flask 风格代码:
from flask import Flask, jsonify
# 初始化 Flask 应用
app = Flask(__name__)
# 绑定根路径 /
@app.route("/")
def index():
return "<h1>欢迎来到道满的 Flask 技术站!</h1>"
# 绑定 /about 路径
@app.route("/about")
def about():
return "<p>这里是道满,专注于简洁好用的 Web 技术</p>"
if __name__ == "__main__":
app.run(debug=True, port=5001)
启动这段代码,访问 http://127.0.0.1:5001 和 http://127.0.0.1:5001/about 就能看到对应的页面了。
1.2 装饰器的“糖衣”原理(简单版)
很多新手可能会觉得装饰器很神秘,但在 Flask 路由里,它只是语法糖——本质是把函数塞进 app 的路由映射表。
# 上面的 @app.route("/") 等价于下面这段原生 Python:
def unwrapped_index():
return "<h1>欢迎来到道满的 Flask 技术站!</h1>"
# 调用 app.route() 先拿到一个「函数改造器」
# 再把改造器应用到 unwrapped_index 上,覆盖原函数名
index = app.route("/")(unwrapped_index)
知道这个原理就够了,平时我们还是用装饰器,代码更清晰。
2. 动态路由:让 URL 带“变量”
静态的 /about 只能展示固定内容,要是想展示不同用户的个人页、不同 ID 的文章,总不能写几千个 @app.route("/user/john") 吧?这时候就需要路径变量(动态路由)。
2.1 三种最常用的变量写法
from flask import Flask
app = Flask(__name__)
# 1. 默认是字符串(不含斜杠)
@app.route("/user/<username>")
def show_user(username):
# 变量名会自动传给处理函数当参数
return f"<h1>用户资料页:{username}</h1>"
# 2. int 类型(自动转成整数,不是整数会 404)
@app.route("/post/<int:post_id>")
def show_post(post_id):
return f"<h1>博客文章 ID:{post_id}</h1>"
# 3. path 类型(可以带斜杠,适合文件路径、多级分类)
@app.route("/file/<path:file_path>")
def show_file(file_path):
return f"正在访问文件:{file_path}"
if __name__ == "__main__":
app.run(debug=True)
2.2 完整变量类型速查表
其中 any 转换器适合处理「固定选项但不想写多个路由」的场景:
@app.route("/order/<any(pending, completed, canceled):status>")
def show_order_list(status):
return f"正在查看状态为「{status}」的订单列表"
# ✅ 访问 /order/pending → 正常
# ✅ 访问 /order/canceled → 正常
# ❌ 访问 /order/shipping → 404 (不在 any 指定的范围内)
2.3 多个变量组合(博客归档示例)
变量可以任意组合,只要符合 URL 规范就行:
from flask import Flask
from datetime import datetime
app = Flask(__name__)
@app.route("/blog/<int:year>/<int:month>/<slug>")
def blog_archive(year, month, slug):
# 验证一下日期的合法性(可选,避免非法日期如 2026/13)
try:
target_date = datetime(year, month, 1)
except ValueError:
return "日期格式不正确!", 400
return f"""
<h1>{target_date.strftime('%Y年%m月')}的文章</h1>
<p>文章标题链接:{slug}</p>
"""
if __name__ == "__main__":
app.run(debug=True)
3. HTTP 方法:控制请求的“动作”
浏览器默认的请求方法是 GET,但 Web 开发里还有 POST、PUT、DELETE 等——这些方法本质是告诉服务器“你要对这个 URL 对应的资源做什么”。
3.1 多方法绑定(登录表单示例)
登录页面需要两个动作:用 GET 展示表单,用 POST 提交表单。我们可以用 methods 参数绑定多个方法:
from flask import Flask, render_template, request, redirect, url_for, flash
app = Flask(__name__)
# flash 需要 secret_key 加密 session,随便设一个就行
app.secret_key = "your-secret-key-keep-it-safe-in-production"
@app.route("/login", methods=["GET", "POST"])
def login():
# request.method 可以获取当前请求的方法
if request.method == "POST":
# 从 request.form 获取 POST 表单数据
username = request.form.get("username")
password = request.form.get("password")
# 这里省略数据库验证逻辑,直接模拟成功
flash("登录成功!")
# 登录成功后跳转到首页(用下一节讲的 url_for)
return redirect(url_for("index"))
# GET 请求就展示登录表单
return render_template("login.html")
@app.route("/")
def index():
return render_template("index.html")
if __name__ == "__main__":
app.run(debug=True)
3.2 RESTful 风格的路由设计
REST 是目前最流行的 API 设计原则,核心是“URL 描述资源,HTTP 方法描述动作”。比如对“文章”这个资源:
注意:浏览器原生只支持 GET 和 POST,如果要在 HTML 里用 PUT/PATCH/DELETE,需要用 <input type="hidden" name="_method" value="PUT"> 加上 Flask 的 methodoverride 中间件(这里暂时不展开,后面扩展阅读会提)。
4. URL 反向生成:告别硬编码的烦恼
硬编码 URL 是开发中的大忌——比如你把 /about 改成了 /about-us,所有引用这个 URL 的地方(HTML 链接、Python 重定向)都要改,很容易漏。Flask 提供了 url_for 函数,通过处理函数的名字生成 URL,完美解决这个问题。
4.1 url_for 的基础用法
from flask import Flask, url_for, redirect
app = Flask(__name__)
@app.route("/")
def index():
# 测试在代码里生成 URL
test_url1 = url_for("show_user", username="alice") # → /user/alice
test_url2 = url_for("blog_archive", year=2026, month=3, slug="flask-routing") # → /blog/2026/3/flask-routing
return f"""
<h1>道满的 Flask 技术站</h1>
<a href='{url_for("about")}'>关于我</a><br>
<a href='{test_url1}'>Alice 的个人页</a><br>
<a href='{test_url2}'>Flask 路由教程</a><br>
"""
@app.route("/about")
def about():
return "关于道满"
@app.route("/user/<username>")
def show_user(username):
return f"用户资料页:{username}"
@app.route("/blog/<int:year>/<int:month>/<slug>")
def blog_archive(year, month, slug):
return f"{year}年{month}月的文章:{slug}"
# 跳转到某个页面
@app.route("/goto-alice")
def goto_alice():
return redirect(url_for("show_user", username="alice"))
if __name__ == "__main__":
app.run(debug=True)
4.2 在 Jinja2 模板中用 url_for
实际开发中,HTML 链接大多写在 Jinja2 模板里,用法和 Python 代码里几乎一样:
<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>道满的 Flask 技术站</title>
</head>
<body>
<h1>道满的 Flask 技术站</h1>
<!-- 生成关于页的链接 -->
<a href="{{ url_for('about') }}">关于我</a><br>
<!-- 生成带变量的链接 -->
<a href="{{ url_for('show_user', username='bob') }}">Bob 的个人页</a><br>
<!-- 生成带额外查询参数的链接(自动拼在 ? 后面) -->
<a href="{{ url_for('blog_archive', year=2026, month=3, slug='flask-routing', sort='latest') }}">
最新的 Flask 路由教程
</a>
</body>
</html>
5. 唯一 URL:Flask 的“智能”尾部斜杠处理
你有没有注意到,访问 https://flask.palletsprojects.com/ 和 https://flask.palletsprojects.com 是一样的,但访问某个具体页面的 /about/ 和 /about 可能不一样?Flask 在这方面做了标准化处理,帮我们避免了“同一个页面有两个 URL”的 SEO 问题。
5.1 Flask 的默认行为
from flask import Flask
app = Flask(__name__)
# 路由没有尾部斜杠 → 类似文件
@app.route("/about")
def about():
return "关于页面(没有尾部斜杠)"
# 路由有尾部斜杠 → 类似文件夹
@app.route("/blog/")
def blog_list():
return "博客列表(有尾部斜杠)"
if __name__ == "__main__":
app.run(debug=True)
测试一下:
- 访问
/about → 正常返回
- 访问
/about/ → Flask 自动 301 重定向到 /about(保证唯一 URL)
- 访问
/blog/ → 正常返回
- 访问
/blog → Flask 自动 301 重定向到 /blog/(保证唯一 URL)
5.2 要不要加尾部斜杠?
建议:
- 静态页面(如
/about、/contact)→ 不加,符合文件的直觉
- 列表/集合页面(如
/blog/、/users/)→ 加,符合文件夹的直觉
- API 接口(如
/api/articles、/api/users/123)→ 统一不加,更简洁
6. 小结
今天我们学了 Flask 路由的所有核心功能:
- 基础装饰器:
@app.route("/path") 绑定 URL 和处理函数
- 动态路由:
<converter:var> 灵活带变量
- HTTP 方法:
methods=["GET", "POST"] 控制请求动作,尝试 RESTful 设计
- 反向生成:
url_for("handler", arg=val) 告别硬编码
- 唯一 URL:理解 Flask 的尾部斜杠重定向
💡 核心设计原则:URL 应该是可预测的、描述资源的、唯一的。
🔗 扩展阅读