Flask 的設計決策

如果您好奇 Flask 為何以目前的方式做事,而不是以其他方式,本節適合您。這應該能讓您了解一些乍看之下可能顯得武斷和令人驚訝的設計決策,尤其是在與其他框架直接比較時。

顯式的應用程式物件

基於 WSGI 的 Python Web 應用程式必須有一個中央可調用物件來實作實際的應用程式。在 Flask 中,這是 Flask 類別的實例。每個 Flask 應用程式都必須自行建立此類別的實例,並將模組名稱傳遞給它,但為什麼 Flask 不能自己做呢?

如果沒有這樣一個顯式的應用程式物件,以下程式碼

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World!'

會變成這樣

from hypothetical_flask import route

@route('/')
def index():
    return 'Hello World!'

這樣做有三個主要原因。最重要的原因是隱式的應用程式物件要求在同一時間只能有一個實例。有一些方法可以使用單一應用程式物件來偽造多個應用程式,例如維護應用程式堆疊,但這會導致一些問題,我不會在此詳細概述。現在的問題是:微框架何時需要同時擁有多個應用程式?單元測試就是一個很好的例子。當您想要測試某些東西時,建立一個最小的應用程式來測試特定行為會非常有幫助。當應用程式物件被刪除時,它分配的所有資源都會被再次釋放。

當您在程式碼中擁有一個顯式的物件時,另一件可能的事情是您可以子類別化基底類別 (Flask) 以更改特定行為。如果物件是根據未向您公開的類別預先建立的,則這是不可能實現的。

但 Flask 依賴於該類別的顯式實例化的另一個非常重要的原因是:套件名稱。每當您建立 Flask 實例時,通常會將 __name__ 作為套件名稱傳遞給它。Flask 依賴該資訊來正確載入相對於您的模組的資源。憑藉 Python 對反射的出色支援,它可以存取套件以找出範本和靜態檔案的儲存位置 (請參閱 open_resource())。現在顯然有一些框架不需要任何配置,並且仍然能夠載入相對於您的應用程式模組的範本。但它們必須使用目前的工作目錄,這是一種非常不可靠的方式來判斷應用程式的位置。目前的工作目錄是進程範圍的,如果您在一個進程中執行多個應用程式(這可能會在 Web 伺服器中發生而您不知道),則路徑將會錯誤。更糟的是:許多 Web 伺服器不會將工作目錄設定為您的應用程式的目錄,而是設定為文件根目錄,而文件根目錄不一定是相同的資料夾。

第三個原因是「顯式優於隱式」。該物件是您的 WSGI 應用程式,您不必記住任何其他內容。如果您想要套用 WSGI 中介軟體,只需包裝它即可(儘管有更好的方法可以做到這一點,這樣您就不會遺失對應用程式物件的參考 wsgi_app())。

此外,這種設計使得可以使用工廠函數來建立應用程式,這對於單元測試和類似的事情非常有幫助 (應用程式工廠)。

路由系統

Flask 使用 Werkzeug 路由系統,該系統旨在按複雜性自動排序路由。這表示您可以以任意順序宣告路由,它們仍然可以按預期工作。如果您想要正確實作基於裝飾器的路由,這是必需的,因為當應用程式被拆分為多個模組時,裝飾器可能會以未定義的順序觸發。

Werkzeug 路由系統的另一個設計決策是 Werkzeug 中的路由嘗試確保 URL 是唯一的。Werkzeug 在這方面做得非常徹底,如果路由不明確,它會自動重新導向到規範 URL。

單一範本引擎

Flask 決定使用單一範本引擎:Jinja2。為什麼 Flask 沒有可插拔的範本引擎介面?您顯然可以使用不同的範本引擎,但 Flask 仍然會為您配置 Jinja2。雖然 Jinja2 總是 被配置的限制可能會消失,但捆綁一個範本引擎並使用它的決定不會改變。

範本引擎就像程式語言,每個引擎都對事物的運作方式有一定的理解。表面上,它們的工作方式都相同:您告訴引擎評估具有一組變數的範本,並將傳回值作為字串。

但相似之處僅止於此。例如,Jinja2 具有廣泛的篩選器系統、一種特定的範本繼承方式、對可重複使用區塊(巨集)的支援,這些區塊可以從範本內部和 Python 程式碼中使用、支援迭代範本渲染、可配置語法等等。另一方面,像 Genshi 這樣的引擎基於 XML 流評估,透過考慮 XPath 的可用性等來進行範本繼承。另一方面,Mako 將範本視為類似於 Python 模組。

當談到將範本引擎與應用程式或框架連接時,不僅僅是渲染範本。例如,Flask 使用 Jinja2 廣泛的自動跳脫支援。它還提供了從 Jinja2 範本存取巨集的方法。

一個不會剝奪範本引擎獨特功能的範本抽象層本身就是一門科學,對於像 Flask 這樣的微框架來說,這項工作太過龐大了。

此外,擴充套件可以輕鬆地依賴於一種範本語言的存在。您可以輕鬆地使用您自己的範本語言,但擴充套件仍然可以依賴 Jinja 本身。

「微型」是什麼意思?

「微型」並不表示您的整個 Web 應用程式必須適合放在單一 Python 檔案中(儘管它當然可以),也不表示 Flask 缺乏功能。「微框架」中的「微型」表示 Flask 旨在保持核心簡單但可擴充。Flask 不會為您做太多決定,例如使用哪個資料庫。它確實做出的那些決定,例如使用哪個範本引擎,都很容易更改。其他一切都取決於您,因此 Flask 可以成為您需要的一切,而不會成為您不需要的任何東西。

預設情況下,Flask 不包含資料庫抽象層、表單驗證或任何其他已經存在可以處理這些功能的不同程式庫的東西。相反,Flask 支援擴充套件,以將此類功能新增到您的應用程式中,就像它是在 Flask 本身中實作的一樣。眾多擴充套件提供資料庫整合、表單驗證、上傳處理、各種開放式身份驗證技術等等。Flask 可能是「微型」的,但它已準備好在各種需求下投入生產使用。

為什麼 Flask 稱自己為微框架,但它卻依賴於兩個程式庫(即 Werkzeug 和 Jinja2)。為什麼不應該這樣做呢?如果我們看看 Web 開發的 Ruby 方面,我們在那裡有一個非常類似於 WSGI 的協定。只是在那裡它被稱為 Rack,但除此之外,它看起來非常像 Ruby 的 WSGI 演繹。但幾乎 Ruby 領域中的所有應用程式都不是直接與 Rack 一起工作,而是在與同名程式庫之上工作。這個 Rack 程式庫在 Python 中有兩個等效的程式庫:WebOb(以前稱為 Paste)和 Werkzeug。Paste 仍然存在,但根據我的理解,它有點被棄用,轉而支持 WebOb。WebOb 和 Werkzeug 的開發並肩開始,心中有相似的想法:成為 WSGI 的良好實作,供其他應用程式利用。

Flask 是一個框架,它利用 Werkzeug 已經完成的工作來正確介接 WSGI(有時可能是一項複雜的任務)。由於 Python 套件基礎架構的最新發展,具有依賴項的套件不再是問題,並且反對擁有依賴於其他程式庫的程式庫的理由很少。

執行緒區域變數

Flask 使用執行緒區域物件(實際上是上下文區域物件,它們也支援 greenlet 上下文)來處理請求、會話和您可以放置自己的東西的額外物件 (g)。為什麼要這樣做?這不是一個壞主意嗎?

是的,使用執行緒區域變數通常不是一個好主意。它們會為不基於執行緒概念的伺服器帶來麻煩,並使大型應用程式更難維護。但是 Flask 並非專為大型應用程式或非同步伺服器而設計。Flask 希望讓編寫傳統 Web 應用程式變得快速而簡單。

Async/await 和 ASGI 支援

Flask 透過在單獨的執行緒上執行協程,而不是像 async-first (ASGI) 框架那樣在主執行緒上使用事件迴圈來支援檢視函數的 async 協程。為了讓 Flask 與在 Python 中引入 async 之前建置的擴充套件和程式碼保持向後相容性,這是必要的。與 ASGI 框架相比,這種妥協引入了效能成本,這是由於執行緒的額外負擔造成的。

由於 Flask 的程式碼與 WSGI 的關聯程度,目前尚不清楚 Flask 類別是否可以同時支援 ASGI 和 WSGI。Werkzeug 目前正在進行與 ASGI 合作的工作,這最終也可能在 Flask 中實現支援。

請參閱 使用 async 和 await 以獲取更多討論。

Flask 是什麼,Flask 不是什麼

Flask 永遠不會有資料庫層。它不會有表單程式庫或任何其他類似的東西。Flask 本身只是橋接到 Werkzeug 以實作適當的 WSGI 應用程式,並橋接到 Jinja2 以處理範本。它還綁定到一些常見的標準程式庫套件,例如日誌記錄。其他一切都取決於擴充套件。

為什麼會這樣?因為人們有不同的偏好和需求,如果 Flask 強制將任何這些內容納入核心,它就無法滿足這些需求。大多數 Web 應用程式都需要某種範本引擎。然而,並非每個應用程式都需要 SQL 資料庫。

隨著您的程式碼庫的成長,您可以自由地做出適合您專案的設計決策。Flask 將繼續為 Python 提供的最佳功能提供非常簡單的膠合層。您可以在 SQLAlchemy 或其他資料庫工具中實作進階模式,根據需要引入非關係資料持久性,並利用為 WSGI(Python Web 介面)建置的與框架無關的工具。

Flask 的想法是為所有應用程式建立良好的基礎。其他一切都取決於您或擴充套件。