工厂函数把「建应用」推迟到被调用的时候,好处是测试能传一套完全不同的配置。 代价是 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 秒实验
- 把 run.py 删掉,直接
FLASK_APP=web试一次,看它能不能自己找到工厂函数。 - 在模板里写死 /static/css/app.css,再把应用挂到 /app 前缀下,看样式还在不在。
- 把 templates 改名成 tpl,看 render_template 报什么错。
常见误区
- 把
app = Flask(__name__)又写回模块顶层:工厂函数白写了,测试还是和线上共用一套配置。 - 静态路径写死:应用换前缀后样式全 404,而且不报错,只是变丑。
- FLASK_APP 指到包却没写工厂:启动时报找不到 Flask 应用, 提示信息里那串候选名字就是它找过的位置。
- 模板放进 web 根目录:render_template 找不到,报的是 TemplateNotFound,而不是「路径错了」。
自测三问(点开对答案)
1. 工厂函数解决了什么问题? —— 让「建应用」这个动作可以被调用多次,每次带一套不同配置。 2. FLASK_APP=run.py 时 flask 命令做了什么? —— import run 这个模块,取出里面的 app 变量。 3. 模板文件为什么可以只写文件名? —— 模板目录是配置好的,render_template 在那个目录里按名字找。骨架搭好了,下一篇往里接数据库:装扩展、三行接入、一个地址。
- ☐ (本篇完,下一篇 2.3 设置 SQLAlchemy)