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

一个 Flask 应用只要三样:应用实例、路由装饰器、视图函数。 除错模式多给的是一整页调用栈:实测同一个 500,页面从 265 字节变成 12805 字节。 调试页不是 Flask 渲染的,是外面那层中间件渲染的——test client 看不到它。

📝 本章和前面几章的关系

这一篇对应教材 1.2 节的前三小节:建工作目录、启动应用、何谓除错模式。 上一篇讲了分工,这一篇把最小的那一份代码跑起来,作为后面所有代码的起点。

一、三行骨架

教材 1.2.1 从建目录讲起:新建一个文件夹,里面放一个 app.py。然后写三样东西。

from flask import Flask
app = Flask(__name__)

@app.route("/")
def index():
    return "Hello, World!"

Flask(__name__) 里那个参数很要紧:它告诉 Flask 「以哪个包的位置为基准去找模板和静态文件」。 写 __name__ 就是「以当前文件所在目录为准」。 这是全书写错次数最多的一个字面量。写成别的字符串,模板立刻找不到。

二、路由里的变量

路由规则里可以带尖括号变量,函数就有同名参数接住它。 /greet/<name> 匹配 /greet/张三,函数收到 name="张三"。 但 /greet 本身不匹配——少一段就是 404。 这条规则比想象中严格。很多人以为「不传就默认空字符串」,实际是「规则不匹配,直接 404」。

想让它可选,得写两条规则,或者给函数参数一个默认值再挂第二条路由。

三、除错模式到底多给了什么

关掉除错时,出错返回一个 265 字节的通用页,只有一句「Internal Server Error」,什么都看不出来。 打开之后,同一个错误返回 12805 字节:异常类名、出错的源码行、完整的调用栈,每一层都能点开看变量。 这两个数差了 48 倍,差的不是排版,是「能不能自己查出来」。没有这一页, 你只能靠 print 一遍遍试。

教材在 1.2.3 节强调:这是开发期工具,上线必须关。原因下面说。

四、为什么 test client 看不到调试页

这次实测踩到的一个坑,值得单独说:开着 DEBUG 用 test client 打,拿到的还是那个 265 字节的页面。 因为调试页不是 Flask 渲染的。 它是 Werkzeug 的一个 WSGI 中间件(DebuggedApplication)在外面套的一层, flask run --debug 才装它。 test client 直接调应用本身,没经过中间件,所以看不到。 要复现,得自己把那层中间件套上再发请求——Demo 就是这么做的。

这件事的启示:报错页面的样子,取决于你在哪一层看。线上经过 Nginx、Gunicorn 再看, 又是另一个样子。

五、除错模式的两个副作用与一个风险

副作用 好处 风险
自动重载 改完文件不用重启 多一个监控线程,稍慢
交互式除错器 浏览器里能执行表达式 本质是远程执行入口

自动重载靠的是一个后台线程盯着文件修改时间,改了就把进程重启。 所以它偶尔会重启两次,别以为是自己写错了。 交互式除错器才是风险所在。它允许在浏览器里执行任意 Python 代码,只靠一个 PIN 码拦着。 放在公网上等于开了一个后门。 教材也提醒过:上线前一定关掉,而且要确认环境变量里没有残留的 FLASK_DEBUG。

六、动手 Demo:最小应用与除错模式

先把三行骨架跑起来,再把除错模式打开和关掉各打一次 500,比页面大小。 最后一段会解释为什么 test client 看不到调试页。

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "Hello, World!"

print(app.test_client().get("/").get_data(as_text=True))

实跑输出:

--------------------------------------------------------------------
最小应用与除错模式
--------------------------------------------------------------------

  ① 一个应用的最小骨架
  --------------------------------------------------------------------

  [代码原样]
  | from flask import Flask
  |
  | app = Flask(__name__)
  |
  | @app.route("/")
  | def index():
  |     return "Hello, World!"
  三样东西:应用实例、路由装饰器、视图函数。少一样都跑不起来。

  GET / -> 200 'Hello, World!'
  GET /greet/张三 -> '你好,张三'
  GET /greet -> 404(没给变量,这条规则就不匹配)

  ② 除错模式到底多给了什么
  --------------------------------------------------------------------
  DEBUG=True(外面套一层除错中间件,flask run --debug 就干这件事)
  -> 500 INTERNAL SERVER ERROR,页面 12805 字节
  页面里有异常类名 ZeroDivisionError 吗 -> True
  页面里有源码行吗 -> True
  DEBUG=False -> 500,页面 265 字节
  页面里有异常类名吗 -> False
  ! 差的就是这一页:除错模式把异常、源码、调用栈全贴出来。
  它在开发时救命,在上线时要命——所以绝不能在公网上开。
  顺带一个容易困惑的点:直接用 test client 打,就算开着 DEBUG,看到的也是那个 265 字节的通用页。
  原因是调试页不是 Flask 渲染的,是外面那层中间件渲染的;
  test client 走的是应用本身,没经过中间件。

  ③ 除错模式的两个副作用
  --------------------------------------------------------------------

| 副作用 | 说明 |
|---|---|
| 自动重载 | 改完文件不用手动重启 |
| 交互式除错器 | 浏览器里能执行任意表达式(只在终端里敲 PIN 才能进) |

  第二条是风险所在:除错器本质是一个远程执行入口。教材在 0.4.4 节也提醒过,上线前一定关掉。
  这一篇的所有请求都走 test client,所以不用真起服务器也能验证除错行为。

/greet 不带变量访问是 404,不是空字符串——规则不匹配就是不匹配。 两个 500 页面:12805 字节对 265 字节,差的正是「能不能自己查出来」。 调试页要经过 DebuggedApplication 这层中间件才渲染,所以 test client 直接打是看不到的。

30 秒实验

  1. 给 index() 加一个 1/0,把 DEBUG 关掉再请求,看返回什么。
  2. 把 /greet/<name> 改成 /greet/<name>/,再访问 /greet/张三(不带斜杠), 看 Flask 会不会自动补斜杠跳转。
  3. 在 return 里返回一个列表,看 Flask 报什么错——想一想为什么。

常见误区

  1. 上线还开着 DEBUG:调试器等于远程执行入口,这是真实发生过的入侵路径。
  2. 以为 Flask(__name__) 里随便写:它决定模板与静态文件的基准目录。
  3. 用 print 代替日志:print 不带级别、不带时间、没法统一关掉。
  4. 以为 test client 等价于真实服务器:中间件、静态文件、并发都不在它的路径上。
自测三问(点开对答案) 1. `Flask(__name__)` 的第一个参数决定什么? —— 以哪个包的位置为基准去找模板和静态文件。写错模板就找不到。 2. 除错模式多给的那一页是谁渲染的? —— Werkzeug 的 DebuggedApplication 中间件,不是 Flask 本身,所以 `flask run --debug` 才有。 3. 为什么 `/greet` 访问会 404 而不是给空名字? —— 路由规则要求路径里必须有那一段,少了就不匹配,匹配不到直接 404,函数不会被调用。

应用能跑了,接下来是配置:密钥、数据库地址、邮件账号这些东西不能写死在代码里。 下一篇讲 .env 与环境变量。

相关笔记