《Flask 入門教程》第 10 章:組織你的代碼
雖然我們的程序開發已經完成,但隨著功能的增多,把所有代碼放在 app.py 里會讓後續的開發和維護變得麻煩。這一章,我們要對項目代碼進行一次重構,讓項目組織變得更加合理。
Flask 對項目結構沒有固定要求,你可以使用單腳本,也可以使用包。這一章我們會學習使用包來組織程序。
先來看看我們目前的項目文件結構:
├── .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
使用包組織代碼
我們會創建一個包,然後把 app.py 中的代碼按照類別分別放到多個模塊里。下面是我們需要執行的一系列操作(這些操作你也可以使用文件管理器或編輯器完成):
$ mkdir watchlist # 創建作為包的文件夾
$ mv static templates watchlist # 把 static 和 templates 文件夾移動到 watchlist 文件夾內
$ cd watchlist # 切換進包目錄
$ touch __init__.py views.py errors.py models.py commands.py # 創建多個模塊
我們把這個包稱為程序包,包里目前包含的模塊和作用如下所示:
- __init__.py 包構造文件,創建程序實例
- views.py 視圖函數
- errors.py 錯誤處理函數
- models.py 模型類
- commands.py 命令函數
提示 除了包構造文件外,其他的模塊文件名你可以自由修改,比如 views.py 也可以叫 routes.py。
創建程序實例,初始化擴展的代碼放到包構造文件里(__init__.py),如下所示:
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] = dev
# 注意更新這裡的路徑,把 app.root_path 添加到 os.path.dirname() 中
# 以便把文件定位到項目根目錄
app.config[SQLALCHEMY_DATABASE_URI] = prefix + os.path.join(os.path.dirname(app.root_path), data.db)
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 = login
@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
在構造文件中,為了讓視圖函數、錯誤處理函數和命令函數註冊到程序實例上,我們需要在這裡導入這幾個模塊。但是因為這幾個模塊同時也要導入構造文件中的程序實例,為了避免循環依賴(A 導入 B,B 導入 A),我們把這一行導入語句放到構造文件的結尾。同樣的,load_user()
函數和 inject_user()
函數中使用的模型類也在函數內進行導入。
其他代碼則按照分類分別放到各自的模塊中,這裡不再給出具體代碼,你可以參考源碼倉庫。在移動代碼之後,注意添加並更新導入語句,比如使用下面的導入語句來導入程序實例和擴展對象:
from watchlist import app, db
使用下面的導入語句來導入模型類:
from watchlist.models import User, Movie
以此類推。
組織模板
模塊文件夾 templates 下包含了多個模板文件,我們可以創建子文件夾來更好的組織它們。下面的操作創建了一個 errors 子文件夾,並把錯誤頁面模板都移動到這個 errors 文件夾內(這些操作你也可以使用文件管理器或編輯器完成):
$ cd templates # 切換到 templates 目錄
$ mkdir errors # 創建 errors 文件夾
$ mv 400.html 404.html 500.html errors # 移動錯誤頁面模板到 errors 文件夾
因為錯誤頁面放到了新的路徑,所以我們需要修改代碼中的 3 處模板文件路徑,以 404 錯誤處理函數為例:
@app.errorhandler(400)
def bad_request(e):
return render_template(errors/400.html), 400
單元測試
你也可以將測試文件拆分成多個模塊,創建一個 tests 包來存儲這些模塊。但是因為目前的測試代碼還比較少,暫時不做改動,只需要更新導入語句即可:
from watchlist import app, db
from watchlist.models import Movie, User
from watchlist.commands import forge, initdb
因為要測試的目標改變,測試時的 --source
選項的值也要更新為包的名稱 watchlist
:
$ coverage run --source=watchlist test_watchlist.py
提示 你可以創建配置文件來預先定義 --source
選項,避免每次執行命令都給出這個選項,具體可以參考文檔配置文件章節。
現在的測試覆蓋率報告會顯示包內的多個文件的覆蓋率情況:
$ coverage report
Name Stmts Miss Cover
-------------------------------------------
watchlist\__init__.py 25 1 96%
watchlistcommands.py 35 1 97%
watchlisterrors.py 8 2 75%
watchlistmodels.py 16 0 100%
watchlistviews.py 77 2 97%
-------------------------------------------
TOTAL 161 6 96%
啟動程序
因為我們使用包來組織程序,不再是 Flask 默認識別的 app.py,所以在啟動開發伺服器前需要使用環境變數 FLASK_APP
來給出程序實例所在的模塊路徑。因為我們的程序實例在包構造文件內,所以直接寫出包名稱即可。在 .flaskenv 文件中添加下面這行代碼:
FLASK_APP=watchlist
最終的項目文件結構如下所示:
├── .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
本章小結
對我們的程序來說,這樣的項目結構已經足夠了。但對於大型項目,你可以使用藍本和工廠函數來進一步組織程序。結束前,讓我們提交代碼:
$ git add .
$ git commit -m "Orignize application with package"
$ git push
提示 你可以在 GitHub 上查看本書示常式序的對應 commit:f705408。
進階提示
- 藍本類似於子程序的概念,藉助藍本你可以把程序不同部分的代碼分離開(比如按照功能劃分為用戶認證、管理後台等多個部分),即對程序進行模塊化處理。每個藍本可以擁有獨立的子域名、URL 前綴、錯誤處理函數、模板和靜態文件。
- 工廠函數就是創建程序的函數。在工廠函數內,我們先創建程序實例,並在函數內完成初始化擴展、註冊視圖函數等一系列操作,最後返回可以直接運行的程序實例。工廠函數可以接受配置名稱作為參數,在內部載入對應的配置文件,這樣就可以實現按需創建載入不同配置的程序實例,比如在測試時調用工廠函數創建一個測試用的程序實例。
- 《Flask Web 開發實戰》 第 7 章介紹了使用包組織程序,第 8 章介紹了大型項目結構以及如何使用藍本和工廠函數組織程序。
- 本書主頁 & 相關資源索引:http://helloflask.com/tutorial。
推薦閱讀: