1.2 路由 Routing


1.2 路由 Routing

问题与直觉:URL 怎么和代码对应

想象一个网站:/ 是首页、/about 是关于页、/user/123 是用户 123 的主页。每个 URL 对应一段代码(视图函数)。路由(Routing)就是这张"URL 与代码的对照表"。

Flask 用装饰器实现这个映射——@app.route('/路径') 下面跟的视图函数就是"这个 URL 该执行什么"。这比手动解析 URL 优雅得多,也是 Flask 最核心的 API。

💡 关键直觉:路由 = 网站的"菜单"。客户端点"菜"(URL),路由负责"下单"到正确的厨房(视图函数)。

核心原理:路由的三种写法

1. 静态路由

from flask import Flask app = Flask(__name__) @app.route('/') def index(): return "首页" @app.route('/about') def about(): return "关于我们"

每个固定路径对应一个视图函数。URL 完全匹配才会命中。

2. 动态路由(带参数)

@app.route('/user/<username>') def show_user(username): return f"用户: {username}" @app.route('/post/<int:post_id>') def show_post(post_id): return f"文章 ID: {post_id}"

<username> 是字符串参数,<int:post_id> 限定为整数。**转换器(Converter)**控制参数类型:

转换器 匹配 示例
string 任意非斜杠文本 /user/bob
int 整数 /post/42
float 浮点数 /price/9.5
path 含斜杠的路径 /files/a/b.txt
uuid UUID 字符串 /item/xxx
@app.route('/files/<path:filepath>') def show_file(filepath): return f"文件路径: {filepath}"

3. HTTP 方法限定

@app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': return "处理登录表单" return "显示登录页面"

默认只接受 GET。用 methods 指定可接受的 HTTP 方法(GET/POST/PUT/DELETE 等)。

工程实践要点:url_for 与路由设计

url_for——反向生成 URL

硬编码 URL 的坏处:路径一改,所有地方都要跟着改。url_for 按视图函数名生成 URL:

from flask import url_for @app.route('/') def index(): # 生成 /user/bob 这样的 URL url = url_for('show_user', username='bob') return f"用户页面链接: {url}" # 模板里也用:<a href="{{ url_for('show_user', username='bob') }}">Bob</a>

url_for 的价值:路由改名后,所有引用自动更新;模板与代码统一走 url_for,避免路径散落。

⚠️ 常见坑:动态参数类型不匹配/post/<int:post_id> 访问 /post/abc 会 404(转换器拒绝非整数)——这是预期行为,不是 bug。想宽松匹配用 string。

路由设计原则

  1. 资源导向/user/123/get_user?id=123 清晰
  2. 动词交给 HTTP:GET 查询、POST 创建、PUT 更新、DELETE 删除
  3. 嵌套适度/post/<id>/comment/<cid> 可用,但别过度嵌套
  4. 用 url_for:不硬编码路径

动手实验:动态路由练习

from flask import Flask, url_for app = Flask(__name__) @app.route('/') def index(): links = [url_for('show_user', username='bob'), url_for('show_post', post_id=42)] return f"链接: {links}" @app.route('/user/<username>') def show_user(username): return f"用户 {username}" @app.route('/post/<int:post_id>') def show_post(post_id): return f"文章 {post_id}" if __name__ == '__main__': app.run(debug=True)

访问 /user/bob/post/42/post/abc(会 404),以及首页看 url_for 生成的链接。

进阶:路由的底层机制

Flask 的路由基于 Werkzeug 的 URL Map:注册路由时生成一棵"规则树",请求进来后按 URL 与方法匹配。理解这层有助于调试:

# 查看应用的所有路由规则 for rule in app.url_map.iter_rules(): print(rule.rule, rule.methods)

输出示例:/ {'GET', 'HEAD', 'OPTIONS'}/user/<username> {'GET', ...}调试"路由没生效"时,先查这里——规则是否注册、方法是否匹配。

FAQ:路由高频问题

问:路由顺序有影响吗?
有。Flask 按注册顺序匹配,先注册的先命中。动态路由 /user/<username> 与静态 /user/admin 同时存在时,静态应先注册(或设计上避免冲突)。

问:同一个 URL 能对应多个函数吗?
同一 URL 不同方法可以(如 GET 显示、POST 提交);同 URL 同方法只能有一个视图函数,后注册的覆盖先注册的。

问:路由里能用正则吗?
原生不支持正则转换器,但可自定义转换器(继承 BaseConverter)实现复杂匹配——需要时再学,日常用内置转换器足够。

深入理解:路由的完整细节

strict_slashes:斜杠的严格匹配

@app.route('/hello') # 访问 /hello 或 /hello/ 都行(默认允许尾斜杠重定向) @app.route('/hello/', strict_slashes=False) # 两种情况都匹配 @app.route('/api/', strict_slashes=True) # 只有 /api/ 匹配,/api 报 404

默认情况下,/hello/hello/ 是等价的(Flask 自动做 301 重定向)。API 场景常设置 strict_slashes=True 让两端严格区分。

转换器全解

转换器 匹配内容 示例 说明
string(默认) 任意文本(不含斜杠) <string:name> 最常用
int 整数 <int:uid> 自动转成 int 类型
float 浮点数 <float:price> 自动转 float
path 含斜杠的路径 <path:filepath> 用于文件路径等
uuid UUID 字符串 <uuid:token> 自动校验格式
any 枚举值之一 <any(a,b,c):page> 限定可选值
@app.route('/files/<path:filepath>') def download(filepath): # 访问 /files/docs/readme.md → filepath='docs/readme.md' return f'下载 {filepath}' @app.route('/color/<any(red,green,blue):c>') def color(c): # 只有 /color/red、/color/green、/color/blue 匹配 return f'选择了 {c}'

endpoint:路由的"内部名字"

每个路由都有一个内部标识——endpoint(默认等于视图函数名):

@app.route('/user/<int:uid>', endpoint='user_detail') def show_user(uid): ... # 模板里用 endpoint 生成 URL,而不硬编码路径 # url_for('user_detail', uid=3) → /user/3

用 endpoint 而不是路径的好处:路径改了(比如 /user/<int:uid> 改成 /member/<int:uid>),模板里 url_for('user_detail', uid=3) 自动跟随,不用到处改字符串——这是"集中管理路由"的核心价值。

视图函数返回值的类型

视图函数可以返回多种类型,Flask 会自动处理:

返回值 Flask 的处理
str 作为 HTML 正文返回,状态码 200
dict 自动转 JSON
(值, 状态码) 指定状态码
Response 对象 原样返回
redirect(...) 302 跳转
abort(404) 抛出错误,走错误处理

路由组织:从小到大的演进

  • 小应用:直接在 app.py 里堆 @app.route
  • 中应用:按功能用蓝图分组(第二章讲);
  • 大应用:蓝图 + 模块化包结构(第四章讲)。

提前知道的教训:不要等路由几百个了才想拆分——那时改造成本已高。每新增一个功能模块,就新建一个蓝图,这是职业级开发的默认习惯。

常见路由问题排查

现象 原因 解法
访问返回 404 URL 拼错/路由没注册 检查路径与 app.run 的 host
访问返回 405 方法不匹配 确认 methods 是否包含请求方法
页面报 404 但路由存在 蓝图未注册 检查 register_blueprint
参数取到的是字符串 没用转换器 <int:uid> 自动转 int
url_for 报 BuildError endpoint 拼错 确认 endpoint 名(默认=函数名)

一节小结

  • 路由本质:URL 到视图函数的映射,@app.route 注册。
  • 动态参数<int:uid> 等转换器自动类型转换;path 转换器支持斜杠。
  • HTTP 方法:methods 参数控制路由接受的方法,默认只有 GET。
  • endpoint:路由的内部名字,url_for 用它生成 URL,路径改动自动跟随。
  • strict_slashes:控制尾斜杠是否严格匹配。
  • 返回值类型:字符串/字典/元组/Response/redirect/abort 都可直接返回。

URL 设计实践

路由是用户看到的"门牌号",好的 URL 设计让应用更易用、更易维护。几条实践建议:

用名词表示资源,动词靠 HTTP 方法。 这是 REST 的核心思想(第二章 2.8 会系统讲)。/users/1 表示"用户 1 这个资源",GET /users/1 是读取它,DELETE /users/1 是删除它——同一个 URL,方法不同语义不同。这比 /get_user?id=1/delete_user?id=1 这种动词化 URL 规范得多。

URL 用小写与连字符。 /user-profile/user_profile 更常见、更可读。参数名用 snake_case(Python 惯例)。

保持 URL 简洁、层次清晰。 /posts/<int:pid>/comments 表示"某篇文章的评论",层级关系一目了然。避免过深的嵌套(超过两三层就该考虑扁平化)。

用 url_for 而不是硬编码路径。 这是最重要的习惯——模板、重定向、测试里都通过 endpoint 生成 URL。路径一旦变更,全站自动跟随,不会出现"改了一个路径,五处 404"的惨剧。

一个真实场景:博客系统的路由设计——

GET / 首页,最新文章列表 GET /posts 文章列表(分页) GET /posts/<int:pid> 文章详情 GET /posts/<int:pid>/edit 编辑页(需登录) POST /posts/<int:pid>/edit 提交编辑 POST /posts/<int:pid>/delete 删除(POST 而不是 GET,避免误触发)

注意"删除"用了 POST 而不是 DELETE——浏览器表单只支持 GET/POST,所以实际项目中删除操作常用 POST 提交。这是实践与 REST 理想之间的常见妥协,第二章 2.8 会讨论如何用 API 场景实现更严格的 REST。

路由数量膨胀的信号:当你发现一个文件里路由超过几十个、命名开始重复时,就是该用蓝图拆分的信号了——下一章马上讲。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U