《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.config['SQLALCHEMY_DATABASE_URI'] = prefix + os.path.join(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 NameStmtsMissCover ------------------------------------------- watchlist\__init__.py25196% watchlist\commands.py35197% watchlist\errors.py8275% watchlist\models.py160100% watchlist\views.py77297% ------------------------------------------- TOTAL161696%
啟動程式
因為我們使用包來組織程式,不再是 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:1e9b88c 。
進階提示
- 藍本類似於子程式的概念,藉助藍本你可以把程式不同部分的程式碼分離開(比如按照功能劃分為使用者認證、管理後臺等多個部分),即對程式進行模組化處理。每個藍本可以擁有獨立的子域名、URL 字首、錯誤處理函式、模板和靜態檔案。
- 工廠函式就是建立程式的函式。在工廠函式內,我們先建立程式例項,並在函式內完成初始化擴充套件、註冊檢視函式等一系列操作,最後返回可以直接執行的程式例項。工廠函式可以接受配置名稱作為引數,在內部載入對應的配置檔案,這樣就可以實現按需建立載入不同配置的程式例項,比如在測試時呼叫工廠函式建立一個測試用的程式例項。
- 《Flask Web 開發實戰》 第 7 章介紹了使用包組織程式,第 8 章介紹了大型專案結構以及如何使用藍本和工廠函式組織程式。
- 本書主頁 & 相關資源索引: http:// helloflask.com/tutorial 。