🧪 第2章 建立数据库应用程序
📄 一句话概括

工厂函数把「建应用」推迟到被调用的时候,好处是测试能传一套完全不同的配置。 代价是 flask 命令找不到 app 变量了,得让 FLASK_APP 指向一个能调出应用的模块。 静态文件放 web/static,模板放 web/templates,Flask 按包名自动映射。

📝 本章和前面几章的关系

这一篇对应教材 2.2 节,六小节从改 FLASK_APP 一路走到确认运作。 上一篇讲蓝图的道理,这一篇把目录真的造出来,后面所有代码都往这个骨架里填。

一、目录长这样

run.py
web/
    __init__.py
    models.py
    views/
        __init__.py
        main.py
    templates/
        index.html

三层,不多不少。run.py 在根上负责「把应用拿出来」,web 包装应用本体。 教材的完整版后面还会多出 forms.py、config.py、static/——这一篇先只放必须的四样。 目录结构不是审美问题。名字对不上,import 立刻报错,而报错信息只说「没有这个模块」, 不说「你把目录改名了」。

二、工厂函数为什么值得多写一层

def create_app(config=None):
    app = Flask(__name__)
    app.config.from_mapping(SECRET_KEY="dev-key")
    if config:
        app.config.update(config)
    db.init_app(app)
    app.register_blueprint(main_bp)
    return app

为什么不直接 app = Flask(__name__) 写在模块顶层? 因为那样一 import 就把应用建好了,想换配置只能改环境变量。 工厂函数把这一步推迟。测试的时候 create_app({"TESTING": True}),同一份代码跑出两个不同的应用。 if config: 那三行是关键。没有它,外面传进来的配置全被忽略,测试跑的还是开发库。

这是这一篇唯一需要理解的一句话:应用是「造」出来的,不是「声明」出来的。

三、FLASK_APP 到底指向谁

应用写进工厂之后,flask run 找不到 app 变量了。 解决办法是写一个 run.py:import 工厂、调一次、把结果赋给 app。

# run.py
from web import create_app

app = create_app()

然后 FLASK_APP=run.py。 flask 命令 import 这个模块,取出里面的 app 变量——它只认这个名字,或者 application。 实测 flask routes 返回码 0,列出两条:main.index 与 static。 static 那条是 Flask 自己加的,不用注册。 教材 2.2.2 提醒过:路径写错时不报「找不到文件」,报的是 ImportError,因为它是当模块 import 的。

实测把 web 目录改名成 webx2 之后,flask routes 返回码 2, 报的是 ModuleNotFoundError: No module named 'webx'。

四、静态文件与模板各归位

static 放 CSS、JS、图片;templates 放 HTML。这两个名字是约定,改了也能跑,但要同时改配置。 映射规则:web/static/css/app.css 对应地址 /static/css/app.css,前缀就是 static 这个名字。 模板不一样:render_template("index.html") 里的名字不带 templates 前缀,也不带包名。

文件 放哪 怎么引用
模板 web/templates/index.html render_template("index.html")
静态 web/static/css/app.css url_for("static", ...)
Python web/views/main.py from .views.main import bp

五、模板里怎么引用 CSS

<link rel="stylesheet"
      href="{{ url_for('static', filename='css/app.css') }}">

不能写死 /static/css/app.css。写死的话,将来应用挂到子路径下(比如 /app/),地址就全错了。 url_for 会按当前应用的实际前缀生成地址,改配置不用改模板。 这条和蓝图无关,是 Flask 里所有静态资源的标准写法。

六、目录名写错会报什么

实测把包目录改名而 import 语句没改,报 ModuleNotFoundError: No module named 'webx'。 这类错误的迷惑点在于:报错说「没有这个模块」,而文件明明就在那儿。 要看的是 import 那一行的名字,不是文件系统。 名字对上还有一层:from .views.main import bp 里那个点表示「相对当前包」。 写成 from views.main 会变成绝对导入,跑到别处去找。

这两种错报的是一句话,原因却完全不同。先看到点在不在,再看名字对不对。

七、动手 Demo:项目骨架:工厂函数、FLASK_APP 与目录

这个 Demo 从头造一个完整的小项目:run.py、工厂、模型、蓝图、模板各就各位。 然后让 flask 命令自己认路,最后故意把包目录改名,看它报什么。

# web/__init__.py
from flask import Flask
from .models import db, User
from .views.main import bp as main_bp

def create_app(config=None):
    app = Flask(__name__)
    app.config.from_mapping(SECRET_KEY="dev-key")
    if config:
        app.config.update(config)
    db.init_app(app)
    app.register_blueprint(main_bp)
    return app

实跑输出:

--------------------------------------------------------------------
项目骨架:工厂函数、FLASK_APP 与目录
--------------------------------------------------------------------

  ① 目录长这样
  --------------------------------------------------------------------
  ├── run.py
  └── web/
      ├── __init__.py
      ├── models.py
      ├── templates/
      │   └── index.html
      └── views/
          ├── __init__.py
          └── main.py

  ② 工厂函数为什么值得多写一层
  --------------------------------------------------------------------

  [代码原样]
  | def create_app(config=None):
  |     app = Flask(__name__)
  |     app.config.from_mapping(SECRET_KEY="dev-key")
  |     if config:
  |         app.config.update(config)
  |     db.init_app(app)
  |     app.register_blueprint(main_bp)
  |     return app
  配置在函数里给,测试时可以传一套完全不同的配置进来。

  「应用实例写在模块顶层」的写法,一 import 就把整个应用建好了,想换配置只能改环境变量。
  工厂函数把这一步推迟到调用时。
  ! 代价是 FLASK_APP 不能再指到 app 变量,得指向一个能调出应用的模块。这就是 run.py 的用处。

  ③ flask 命令怎么认到应用
  --------------------------------------------------------------------
  FLASK_APP=run.py,flask routes -> 返回码 0
  Endpoint    Methods  Rule
  ----------  -------  -----------------------
  main.index  GET      /
  static      GET      /static/<path:filename>
  ! FLASK_APP 指 run.py,run.py 里 app = create_app()——flask 命令 import 那个模块,取出 app 变量。

  ④ 静态文件与模板各归位
  --------------------------------------------------------------------
  静态文件放在 web/static/ 下面,Flask 按包名自动映射到 /static/。
  模板放在 web/templates/ 下面,render_template 按名字找,不带路径前缀。

  ⑤ 目录名与包名不一致会怎样
  --------------------------------------------------------------------
  把包目录改名成 webx2 之后,flask routes -> 返回码 2
      from webx import create_app
  ModuleNotFoundError: No module named 'webx'
  ! 报的是 ImportError。目录名要和 import 语句里的包名一致,这是改名时最常见的一处遗漏。

目录树里的 static 是第四节才建的,所以第一张图里没有它。 flask routes 那两条是「应用真的能被命令行找到」的证据,比 flask run 更好用来体检。 最后那个改名实验:返回码 2 加上一句 No module named,就是所有「明明文件在」的错误的真面目。

30 秒实验

  1. 把 run.py 删掉,直接 FLASK_APP=web 试一次,看它能不能自己找到工厂函数。
  2. 在模板里写死 /static/css/app.css,再把应用挂到 /app 前缀下,看样式还在不在。
  3. 把 templates 改名成 tpl,看 render_template 报什么错。

常见误区

  1. 把 app = Flask(__name__) 又写回模块顶层:工厂函数白写了,测试还是和线上共用一套配置。
  2. 静态路径写死:应用换前缀后样式全 404,而且不报错,只是变丑。
  3. FLASK_APP 指到包却没写工厂:启动时报找不到 Flask 应用, 提示信息里那串候选名字就是它找过的位置。
  4. 模板放进 web 根目录:render_template 找不到,报的是 TemplateNotFound,而不是「路径错了」。
自测三问(点开对答案) 1. 工厂函数解决了什么问题? —— 让「建应用」这个动作可以被调用多次,每次带一套不同配置。 2. FLASK_APP=run.py 时 flask 命令做了什么? —— import run 这个模块,取出里面的 app 变量。 3. 模板文件为什么可以只写文件名? —— 模板目录是配置好的,render_template 在那个目录里按名字找。

骨架搭好了,下一篇往里接数据库:装扩展、三行接入、一个地址。

相关笔记