路由表是 Flask 的核心数据结构,一条规则对应一个端点名加一个函数。
五种转换器:string、int、float、path、uuid;转换器同时是校验器。
url_for 按端点名反查地址,改名不担心断链,还会自动做 URL 编码。
这一篇对应教材 1.2.5 节「应用程式路由」,篇幅不长但密度很高。 上一篇的应用只有两条规则,这一篇会长到九条,正好把各种情况铺开。
一、装饰器只是个语法糖
@app.route("/x") 的作用,等价于 app.add_url_rule("/x", "x", 函数)。
两个写法完全等价,差别只在「函数先定义还是先注册」。
装饰器写法要求函数定义在下面,函数写法的函数可以先定义、后注册。
动态加路由只能用后者。比如从配置表里读出一批路径,一个个注册——装饰器做不到,
因为它必须在定义时求值。
教材只讲了装饰器,但路由表里那个 endpoint 名字就是 add_url_rule 的第二个参数,
知道这一点才能理解 url_for。
二、五种转换器
| 写法 | 收什么 | 不收什么 |
|---|---|---|
| 任何不含斜杠的字符串 | 带斜杠的 | |
| 整数 | abc、4.5 | |
| 小数 | abc | |
| 可以带斜杠的路径 | —— | |
| 标准 UUID 格式 | 随便一串字符 |
转换器不只是「取参数」,它同时是校验。类型不对,规则就不匹配,直接 404,
视图函数根本不会被调用。
所以「参数校验」这件事,能在路由层做的就别放到函数里——少一层判断,也少一个漏判的可能。
实测:/post/abc、/post/4.5、/uuid/not-a-uuid 全是 404,而不是 400 或者抛异常。
三、methods 与 405
默认只允许 GET。要收 POST 必须显式声明 methods=["GET", "POST"]。
声明之后,没列出来的方法会返回 405(方法不允许),不是 404。
405 和 404 的区别很实用:405 说明路径对了、方法错了。
HEAD 和 OPTIONS 是自动加的:HEAD 用于只要响应头不要正文的场景,OPTIONS 用于跨域预检。
所以路由表里方法列打印出来要去掉这两个。
四、url_for 为什么值得单独讲
它按端点名加参数生成地址。
改路由规则时,只要端点名不变,所有生成链接的地方都不用动——这是它唯一的价值,但足够大。
它还会做 URL 编码。这次实测 url_for('user', name='张三') 出来的是 %E5%BC%A0%E4%B8%89,
不是原样的中文。
手写路径就不会编码,中文参数在浏览器里可能被截断或乱码。这种 bug 极难查,因为大多数时候又能用。
在模板里更要一律用它:url_for('static', filename='css/app.css'),别手写 /static/css/app.css。
五、路由表的读法
flask routes 打印三列:端点名、方法、规则。按规则排序来看最清楚。
遇到 404,先看这张表:规则在不在、方法对不对、变量名对不对得上。
这张表还能看出「是不是被别的规则抢先匹配了」。/user/<name> 和 /user/list 同时存在时,
谁先注册谁先匹配,顺序错了就永远进不去第二个。
所以固定的路径要写在带变量的路径前面。这一点教材没讲,但排错时一定会遇到。
六、动手 Demo:路由:规则、变量与转换器
这一节建一个九条规则的应用,把五种转换器、多方法、函数写法全部试一遍,最后打印整张路由表。
@app.route("/post/<int:pid>")
def post(pid):
return "pid=%d" % pid
@app.route("/multi", methods=["GET", "POST"])
def multi():
return request.method
# 装饰器之外:先定义,再注册
def legacy():
return "legacy ok"
app.add_url_rule("/legacy", "legacy", legacy)
实跑输出:
--------------------------------------------------------------------
路由:规则、变量与转换器
--------------------------------------------------------------------
① 五种转换器
--------------------------------------------------------------------
| 规则 | 请求的地址 | 状态 | 返回 |
|---|---|---|---|
| /user/<name> | /user/张三 | 200 | name=张三 |
| /post/<int:pid> | /post/42 | 200 | pid=42 |
| /price/<float:p> | /price/19.9 | 200 | p=19.90 |
| /file/<path:fn> | /file/a/b/c.png | 200 | fn=a/b/c.png |
| /uuid/<uuid:u> | /uuid/3f2504e0-4f89-11d... | 200 | u=3f2504e0 |
! 五种转换器各管一类值:string 是默认(收不到 /)、int 只收整数、float 收小数、path 允许斜杠、
uuid 只收标准格式。
② 收不到的时候
--------------------------------------------------------------------
GET /post/abc -> 404
GET /post/4.5 -> 404
GET /uuid/not-a-uuid -> 404
! 转换器不只是「取参数」,它同时是校验:类型不对直接 404,视图函数根本不会被调用。
③ 一个路由挂两个方法
--------------------------------------------------------------------
GET /multi -> 200,method=GET
POST /multi -> 200,method=POST
PUT /multi -> 405(没声明就不让走)
④ 装饰器之外还有函数写法
--------------------------------------------------------------------
add_url_rule 注册的 /legacy -> 200,legacy ok
装饰器 @app.route 内部就是调它,两个写法等价。差别是函数写法可以先定义再注册,方便动态加路由。
⑤ url_for 生成地址
--------------------------------------------------------------------
url_for('idx') -> /
url_for('user') -> /user/%E5%BC%A0%E4%B8%89
url_for('post') -> /post/7
url_for('file_') -> /file/a/b/c.png
用生成的地址真请求一次 -> 200 pid=7
! url_for 传的是「端点名加参数」,不是拼好的字符串。
所以改了规则也不用改代码——这是它比手写路径强的唯一理由,但足够强。
⑥ 路由表全貌
--------------------------------------------------------------------
/ GET idx
/file/<path:fn> GET file_
/legacy GET legacy
/multi GET,POST multi
/post/<int:pid> GET post
/price/<float:p> GET price
/static/<path:filename> GET static
/user/<name> GET user
/uuid/<uuid:u> GET uuid_
共 9 条规则,其中 static 是自动加的。
五种转换器的返回值看得出来类型:int 出来是整数,float 补了两位小数。
三个错误类型全是 404,没有一个 400——转换器不匹配就是不匹配。
PUT /multi 返回 405,说明路径对了但方法没声明。
url_for 生成的中文参数是百分号编码,这是它比手写路径强的地方之一。
30 秒实验
- 加一条
/post/<int:pid>/edit,试试/post/1/edit能不能进。 - 把
/user/<name>改成/user/<name>/,访问不带斜杠的地址看会不会自动跳转。 - 同时保留
/user/<name>和/user/list,把list那条写在后面, 访问/user/list看进的是哪个函数。
常见误区
- 以为路由函数可以不写 methods:默认只有 GET,POST 请求会 405。
- 手写 URL 不用 url_for:改名就断链,中文参数还不编码。
- 固定路径写在变量路径后面:永远匹配不到,而且不报错。
- 在路由里写业务判断:「参数是不是数字」交给转换器,别在函数里再判一次。
自测三问(点开对答案)
1. 转换器除了取参数还干了什么? —— 校验。类型不匹配规则就不成立,直接 404,函数不会被调用。 2. 405 和 404 分别说明什么? —— 405 是路径对了但方法没声明,404 是路径本身就不存在。 3. 为什么中文参数要用 url_for 生成? —— 它会做 URL 编码;手写路径不编码,中文可能被截断或乱码。地址能对上了,接着是页面:下一篇讲 Jinja2 的继承与过滤器,把重复的 HTML 抽到 base 模板里。
- ☐ (本篇完,下一篇 1.5 模板引擎 Jinja2 与 url_for)