路由(Routing)艺术:变量规则、HTTP 方法与唯一 URL
📂 所属阶段:第一阶段 — 破冰启航(基础篇)
🔗 相关章节:初识 Flask · Jinja2 模板引擎
路由,一言以蔽之,就是把浏览器访问的 URL 路径,和我们写的 Python 处理函数一一绑定的机制。Flask 的路由系统灵活、简洁,是它上手快、受欢迎的核心原因之一。今天我们就一起来啃透这个基础但至关重要的模块。
1. 从最简单的路由开始
1.1 基础装饰器写法
Flask 使用 @app.route() 装饰器来定义路由,参数就是你想要绑定的 URL 路径。下面先写一段最直观的代码:
from flask import Flask
# 初始化 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__":
# debug=True 开启调试模式,修改代码自动重启
app.run(debug=True, port=5001)
启动后,在浏览器访问 http://127.0.0.1:5001 和 http://127.0.0.1:5001/about,就可以看到对应页面。
1.2 装饰器的“糖衣”原理(简单版)
很多新手可能会觉得装饰器很神秘,但在 Flask 路由里,它只是语法糖——本质就是把函数注册到应用的路由映射表中。
# 下面的装饰器写法
@app.route("/")
def index():
return "<h1>欢迎</h1>"
# 其实等价于这段原生 Python:
def unwrapped_index():
return "<h1>欢迎</h1>"
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):
# 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):
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 等,它们本质上是告诉服务器“你要对这个资源做什么”。
3.1 多方法绑定(登录表单示例)
登录功能通常需要两个动作:用 GET 展示表单,用 POST 提交表单。我们可以通过 methods 参数让同一个路由处理这两种请求。
from flask import Flask, render_template, request, redirect, url_for, flash
app = Flask(__name__)
# flash 消息需要设置 secret_key
app.secret_key = "your-secret-key-keep-it-safe"
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "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)
💡 小提示:request.form 专用于获取 POST 提交的表单数据;如果上传的是 JSON,可以用 request.get_json()。
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,所有引用这个地址的地方(HTML 链接、重定向代码)都要逐一修改,极易遗漏。Flask 提供了 url_for 函数,通过视图函数的名字来生成对应的 URL,完美解决这一问题。
4.1 url_for 的基础用法
from flask import Flask, url_for, redirect
app = Flask(__name__)
@app.route("/")
def index():
# 在 Python 代码里生成 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}"
# 跳转到 Alice 的个人页
@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 模板中配合使用
实际开发中,链接大多写在 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>
💡 最佳实践:只要你需要生成应用内部的 URL,就无条件使用 url_for,它会让重构变得无比轻松。
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
- 访问
/blog/ → ✅ 正常返回
- 访问
/blog → 🔀 Flask 自动 301 重定向到 /blog/
Flask 的做法是:如果定义的路由没有斜杠,访问带斜杠的地址会被重定向到无斜杠版本;反之亦然。这样始终保证一个资源只有一个权威地址。
5.2 要不要加尾部斜杠?
建议遵循以下直觉:
- 静态页面(如
/about、/contact)→ 不加斜杠,像文件
- 列表/集合型页面(如
/blog/、/users/)→ 加上斜杠,像文件夹
- API 接口(如
/api/articles、/api/users/123)→ 统一不加斜杠,更简洁
6. 小结
今天我们系统学习了 Flask 路由的核心能力:
- 基础装饰器:
@app.route("/path") 绑定 URL 与视图函数
- 动态路由:
<converter:variable> 让 URL 携带可变数据
- HTTP 方法:通过
methods 参数处理不同请求动作,迈向 RESTful 设计
- 反向生成:
url_for("view_name", arg=val) 消灭硬编码
- 唯一 URL:理解 Flask 的尾部斜杠重定向机制
💡 核心设计原则:URL 应该是可预测的、描述资源的、唯一的。
🔗 扩展阅读