<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>项目结构 &#8211; 李辉 / Grey Li</title>
	<atom:link href="https://greyli.com/tag/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/feed/" rel="self" type="application/rss+xml" />
	<link>https://greyli.com</link>
	<description>一个编程和写作爱好者的在线记事本</description>
	<lastBuildDate>Thu, 06 Nov 2025 11:36:11 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.9.26</generator>

<image>
	<url>https://greyli.com/wp-content/uploads/2025/03/avatar-500-compressed-144x144.jpg</url>
	<title>项目结构 &#8211; 李辉 / Grey Li</title>
	<link>https://greyli.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>《Flask 入门教程》第 10 章：组织你的代码</title>
		<link>https://greyli.com/flask-tutorial-chapter-10-organize/</link>
		<comments>https://greyli.com/flask-tutorial-chapter-10-organize/#comments</comments>
		<pubDate>Thu, 24 Jan 2019 01:41:14 +0000</pubDate>
		<dc:creator><![CDATA[李辉]]></dc:creator>
				<category><![CDATA[计算机与编程]]></category>
		<category><![CDATA[Flask]]></category>
		<category><![CDATA[Flask 入门教程]]></category>
		<category><![CDATA[项目结构]]></category>

		<guid isPermaLink="false">http://greyli.com/?p=2218</guid>
		<description><![CDATA[虽然我们的程序开发已经完成，但随着功能的增多，把所有代码放在 app.py 里会让后续的开发和维护变得麻烦。这 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>
	虽然我们的程序开发已经完成，但随着功能的增多，把所有代码放在 app.py 里会让后续的开发和维护变得麻烦。这一章，我们要对项目代码进行一次重构，让项目组织变得更加合理。
</p>
<p>
	Flask 对项目结构没有固定要求，你可以使用单脚本，也可以使用包。这一章我们会学习使用包来组织程序。
</p>
<p>
	先来看看我们目前的项目文件结构：
</p>
<pre>
<code>├── .flaskenv
├── Pipfile
├── Pipfile.lock
├── app.py
├── static
│   ├── favicon.ico
│   ├── images
│   │   ├── avatar.png
│   │   └── totoro.gif
│   └── style.css
├── templates
│   ├── 400.html
│   ├── 404.html
│   ├── 500.html
│   ├── base.html
│   ├── edit.html
│   ├── index.html
│   ├── login.html
│   └── settings.html
└── test_watchlist.py
</code></pre>
<h2>
	使用包组织代码<br />
</h2>
<p>
	我们会创建一个包，然后把 app.py 中的代码按照类别分别放到多个模块里。下面是我们需要执行的一系列操作（这些操作你也可以使用文件管理器或编辑器完成）：
</p>
<pre>
$ mkdir watchlist  # 创建作为包的文件夹
$ mv static templates watchlist  # 把 static 和 templates 文件夹移动到 watchlist 文件夹内
$ cd watchlist  # 切换进包目录
$ touch __init__.py views.py errors.py models.py commands.py  # 创建多个模块</pre>
<p>
	我们把这个包称为程序包，包里目前包含的模块和作用如下表所示：
</p>
<table>
<thead>
<tr>
<th>
				模块
			</th>
<th>
				作用
			</th>
</tr>
</thead>
<tbody>
<tr>
<td>
				__init__.py
			</td>
<td>
				包构造文件，创建程序实例
			</td>
</tr>
<tr>
<td>
				views.py
			</td>
<td>
				视图函数
			</td>
</tr>
<tr>
<td>
				errors.py
			</td>
<td>
				错误处理函数
			</td>
</tr>
<tr>
<td>
				models.py
			</td>
<td>
				模型类
			</td>
</tr>
<tr>
<td>
				commands.py
			</td>
<td>
				命令函数
			</td>
</tr>
</tbody>
</table>
<p>
	<strong>提示</strong>&nbsp;除了包构造文件外，其他的模块文件名你可以自由修改，比如 views.py 也可以叫 routes.py。
</p>
<p>
	创建程序实例，初始化扩展的代码放到包构造文件里（__init__.py），如下所示：
</p>
<pre>
import os
import sys

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

# ...

app = Flask(__name__)
app.config['SECRET_KEY'] = &#39;dev&#39;
# 注意更新这里的路径，把 app.root_path 添加到 os.path.dirname() 中
# 以便把文件定位到项目根目录
app.config['SQLALCHEMY_DATABASE_URI'] = prefix + os.path.join(os.path.dirname(app.root_path), &#39;data.db&#39;)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
login_manager = LoginManager(app)

@login_manager.user_loader
def load_user(user_id):
	from watchlist.models import User
	user = User.query.get(int(user_id))
	return user

login_manager.login_view = &#39;login&#39;

@app.context_processor
def inject_user():
	from watchlist.models import User
	user = User.query.first()
	return dict(user=user)

from watchlist import views, errors, commands</pre>
<p>
	在构造文件中，为了让视图函数、错误处理函数和命令函数注册到程序实例上，我们需要在这里导入这几个模块。但是因为这几个模块同时也要导入构造文件中的程序实例，为了避免循环依赖（A 导入 B，B 导入 A），我们把这一行导入语句放到构造文件的结尾。同样的，<code>load_user()</code>&nbsp;函数和&nbsp;<code>inject_user()</code>&nbsp;函数中使用的模型类也在函数内进行导入。
</p>
<p>
	其他代码则按照分类分别放到各自的模块中，这里不再给出具体代码，你可以参考<a href="https://github.com/greyli/watchlist">源码仓库</a>。在移动代码之后，注意添加并更新导入语句，比如使用下面的导入语句来导入程序实例和扩展对象：
</p>
<pre>
from watchlist import app, db</pre>
<p>
	使用下面的导入语句来导入模型类：
</p>
<pre>
from watchlist.models import User, Movie</pre>
<p>
	以此类推。
</p>
<h2>
	组织模板<br />
</h2>
<p>
	模块文件夹 templates 下包含了多个模板文件，我们可以创建子文件夹来更好的组织它们。下面的操作创建了一个 errors 子文件夹，并把错误页面模板都移动到这个 errors 文件夹内（这些操作你也可以使用文件管理器或编辑器完成）：
</p>
<pre>
$ cd templates  # 切换到 templates 目录
$ mkdir errors  # 创建 errors 文件夹
$ mv 400.html 404.html 500.html errors  # 移动错误页面模板到 errors 文件夹</pre>
<p>
	因为错误页面放到了新的路径，所以我们需要修改代码中的 3 处模板文件路径，以 404 错误处理函数为例：
</p>
<pre>
@app.errorhandler(400)
def bad_request(e):
    return render_template(&#39;errors/400.html&#39;), 400</pre>
<h2>
	单元测试<br />
</h2>
<p>
	你也可以将测试文件拆分成多个模块，创建一个 tests 包来存储这些模块。但是因为目前的测试代码还比较少，暂时不做改动，只需要更新导入语句即可：
</p>
<pre>
from watchlist import app, db
from watchlist.models import Movie, User
from watchlist.commands import forge, initdb</pre>
<p>
	因为要测试的目标改变，测试时的&nbsp;<code>--source</code>&nbsp;选项的值也要更新为包的名称&nbsp;<code>watchlist</code>：
</p>
<pre>
$ coverage run --source=watchlist test_watchlist.py</pre>
<p>
	<strong>提示</strong>&nbsp;你可以创建配置文件来预先定义&nbsp;<code>--source</code>&nbsp;选项，避免每次执行命令都给出这个选项，具体可以参考文档<a href="https://coverage.readthedocs.io/en/v4.5.x/config.html" rel="nofollow">配置文件章节</a>。
</p>
<p>
	现在的测试覆盖率报告会显示包内的多个文件的覆盖率情况：
</p>
<pre>
$ coverage report
Name                    Stmts   Miss  Cover
-------------------------------------------
watchlist\__init__.py      25      1    96%
watchlist\commands.py      35      1    97%
watchlist\errors.py         8      2    75%
watchlist\models.py        16      0   100%
watchlist\views.py         77      2    97%
-------------------------------------------
TOTAL                     161      6    96%</pre>
<h2>
	启动程序<br />
</h2>
<p>
	因为我们使用包来组织程序，不再是 Flask 默认识别的 app.py，所以在启动开发服务器前需要使用环境变量&nbsp;<code>FLASK_APP</code>&nbsp;来给出程序实例所在的模块路径。因为我们的程序实例在包构造文件内，所以直接写出包名称即可。在 .flaskenv 文件中添加下面这行代码：
</p>
<pre>
<code>FLASK_APP=watchlist
</code></pre>
<p>
	最终的项目文件结构如下所示：
</p>
<pre>
<code>├── .flaskenv
├── Pipfile
├── Pipfile.lock
├── test_watchlist.py
└── watchlist  # 程序包
    ├── __init__.py
    ├── commands.py
    ├── errors.py
    ├── models.py
    ├── static
    │   ├── favicon.ico
    │   ├── images
    │   │   ├── avatar.png
    │   │   └── totoro.gif
    │   └── style.css
    ├── templates
    │   ├── base.html
    │   ├── edit.html
    │   ├── errors
    │   │   ├── 400.html
    │   │   ├── 404.html
    │   │   └── 500.html
    │   ├── index.html
    │   ├── login.html
    │   └── settings.html
    └── views.py
</code></pre>
<h2>
	本章小结<br />
</h2>
<p>
	对我们的程序来说，这样的项目结构已经足够了。但对于大型项目，你可以使用蓝本和工厂函数来进一步组织程序。结束前，让我们提交代码：
</p>
<pre>
$ git add .
$ git commit -m &quot;Orignize application with package&quot;
$ git push</pre>
<p>
	<strong>提示</strong> 你可以在 GitHub 上查看本书示例程序的对应 commit：<a href="https://github.com/greyli/watchlist/commit/f7054083c8f87f83bf842a1125a3d8d0244b0f62" spellcheck="false">f705408</a>。
</p>
<h2>
	进阶提示<br />
</h2>
<ul>
<li>
		<a href="http://flask.pocoo.org/docs/1.0/blueprints/" rel="nofollow">蓝本</a>类似于子程序的概念，借助蓝本你可以把程序不同部分的代码分离开（比如按照功能划分为用户认证、管理后台等多个部分），即对程序进行模块化处理。每个蓝本可以拥有独立的子域名、URL 前缀、错误处理函数、模板和静态文件。
	</li>
<li>
		<a href="http://flask.pocoo.org/docs/1.0/patterns/appfactories/" rel="nofollow">工厂函数</a>就是创建程序的函数。在工厂函数内，我们先创建程序实例，并在函数内完成初始化扩展、注册视图函数等一系列操作，最后返回可以直接运行的程序实例。工厂函数可以接受配置名称作为参数，在内部加载对应的配置文件，这样就可以实现按需创建加载不同配置的程序实例，比如在测试时调用工厂函数创建一个测试用的程序实例。
	</li>
<li>
		<a href="http://helloflask.com/book/" rel="nofollow">《Flask Web 开发实战》</a>&nbsp;第 7 章介绍了使用包组织程序，第 8 章介绍了大型项目结构以及如何使用蓝本和工厂函数组织程序。
	</li>
<li>
		本书主页 &amp; 相关资源索引：<a href="http://helloflask.com/tutorial">http://helloflask.com/tutorial</a>。
	</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://greyli.com/flask-tutorial-chapter-10-organize/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
