🧪 第1章 建立最基础的应用程序
📄 一句话概括

路由表是 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 秒实验

  1. 加一条 /post/<int:pid>/edit,试试 /post/1/edit 能不能进。
  2. 把 /user/<name> 改成 /user/<name>/,访问不带斜杠的地址看会不会自动跳转。
  3. 同时保留 /user/<name> 和 /user/list,把 list 那条写在后面, 访问 /user/list 看进的是哪个函数。

常见误区

  1. 以为路由函数可以不写 methods:默认只有 GET,POST 请求会 405。
  2. 手写 URL 不用 url_for:改名就断链,中文参数还不编码。
  3. 固定路径写在变量路径后面:永远匹配不到,而且不报错。
  4. 在路由里写业务判断:「参数是不是数字」交给转换器,别在函数里再判一次。
自测三问(点开对答案) 1. 转换器除了取参数还干了什么? —— 校验。类型不匹配规则就不成立,直接 404,函数不会被调用。 2. 405 和 404 分别说明什么? —— 405 是路径对了但方法没声明,404 是路径本身就不存在。 3. 为什么中文参数要用 url_for 生成? —— 它会做 URL 编码;手写路径不编码,中文可能被截断或乱码。

地址能对上了,接着是页面:下一篇讲 Jinja2 的继承与过滤器,把重复的 HTML 抽到 base 模板里。

相关笔记