選擇一個 Python Web 框架:Django vs Flask vs Pyramid

發表於2017-08-31

Pyramid, Django, 和 Flask都是優秀的框架,為專案選擇其中的哪一個都是傷腦筋的事。我們將會用三種框架實現相同功能的應用來更容易的對比三者。也可以直接跳到框架實戰(Frameworks in Action)章節檢視程式碼(code)。

1 簡介

世界上可選的基於Python的web框架有很多。Django, Flask, Pyramid, Tornado, Bottle, Diesel, Pecan, Falcon等等,都在爭取開發者支援。作為一開發者從一堆選擇中篩選出一個來完成專案將會成為下一個大工程。我們今天專注於Flask, Pyramid, 和 Django。它們涵蓋了從小微專案到企業級的web服務。

為了更容易在三者中作出選擇(至少更瞭解它們),我們將用每一個框架構建同樣的應用並比較它們的程式碼,對於每一個方法我們會高亮顯示它的優點和缺點。如果你只想要程式碼,直接跳到框架實戰章節(Frameworks in Action),或者檢視其在Github上的程式碼。

Flask是一個面向簡單需求小型應用的“微框架(microframework)”。Pyramid和Django都是面向大型應用的,但是有不同的擴充性和靈活性。Pyramid的目的是更靈活,能夠讓開發者為專案選擇合適的工具。這意味著開發者能夠選擇資料庫、URL結構、模板型別等等。Django目的是囊括web應用的所有內容,所以開發者只需要開啟箱子開始工作,將Django的模組拉進箱子中。

Django包括一個開箱即用的 ORM ,而Pyramid和 Flask讓開發者自己選擇如何或者是否儲存他們的資料。到目前為止對於非Django的web應用來說最流行的ORM是SQLAlchemy,同時還有多種其他選擇,從 DynamoDBMongoDB 到簡單本地儲存的LevelDB 或樸實的SQLite。Pyramid被設計為可使用任何資料持久層,甚至是還沒有開發出來的。

2、關於框架

Django的”batteries included” 特性讓開發者不需要提前為他們的應用程式基礎設施做決定,因為他們知道Python已經深入到了web應用當中。Django已經內建了模板、表單、路由、認證、基本資料庫管理等等。比較起來,Pyramid包括路由和認證,但是模板和資料庫管理需要額外的庫。

前面為 Flask和Pyramid apps選擇元件的額外工作給那些使用案例不適用標準ORM的開發者提供了更多的靈活性,同樣也給使用不同工作流和模版化系統的開發者們帶來了靈活性。

Flask,作為三個框架裡面最稚氣的一個,開始於2010年年中。Pyramid框架是從Pylons專案開始的,在2010年底獲得 Pyramid這個名字,雖然在2005年就已經發布了第一個版本。Django 2006年釋出了第一個版本,就在Pylons專案(最後叫Pyramid)開始之後。Pyramid和Django都是非常成熟的框架,積累了眾多外掛和擴充套件以滿足難以置信的巨大需求。

雖然Flask歷史相對更短,但它能夠學習之前出現的框架並且把注意力放在了微小專案上。它大多數情況被使用在一些只有一兩個功能的小型專案上。例如 httpbin,一個簡單的(但很強大的)除錯和測試HTTP庫的專案。

3. 社群

最具活力的社群當屬Django,其有80,000個StackOverflow問題和一系列來自開發者和優秀使用者的良好的部落格。Flask和Pyramid社群並沒有那麼大,但它們的社群在郵件列表和IRC上相當活躍。StackOverflow上僅有5,000個相關的標籤,Flask比Django小了15倍。在Github上,它們的star近乎相當,Django有11,300個,Flask有10,900個。

三個框架都使用的是BSD衍生的協議。FlaskDjango的協議是BSD 3條款,Pyramid的Repoze Public License RPL是BSD協議 4條款的衍生。

4. Bootstrapping

Django和Pyramid都內建bootstrapping工具。Flask沒有包含類似的工具,因為Flask的目標使用者不是那種試圖構建大型MVC應用的人。

4.1 Flask

Flask的hello world應用非常的簡單,僅僅單個Python檔案的7行程式碼就夠了。

這是Flask沒有bootstrapping工具的原因:沒有它們的需求。從Flask主頁上的Hello World特性看,沒有構建Python web應用經驗的開發者可以立即開始hacking。

對於各部分需要更多分離的專案,Flask有blueprints。例如,你可以將所有使用者相關的函式放在users.py中,將銷售相關的函式放在ecommerce.py中,然後在site.py中新增引用它們來結構化你的Flask應用。我們不會深入這個功能,因為它超出了我們展示demo應用的需求。

4.2 Pyramid

Pyramid 的 bootstrapping工具叫 pcreate,是Pyramid的組成部分. 之前的 Paste 工具套裝提供了 bootstrapping ,但是從那之後被 Pyramid專用工具鏈替代了。

Pyramid 比 Flask 適用於更大更復雜的應用程式. 因為這一點,它的 bootstrapping工具建立更大的專案骨架. Pyramid 同樣加入了基本的配置檔案,一個例子模版和用於將程式打包上傳到 Python Package Index的所有檔案。

作為最後描述的框架,Pyramid的bootstrapper非常靈活. 不侷限於一個預設的程式;pcreate 可以使用任意數量的專案模版. 包括我們上面用到的pcreate裡面的”starter”的模版, 還有 SQLAlchemy- ,ZODB-支援scaffold專案. 在 PyPi可以發現已經為Google App Engine, jQuery Mobile, Jinja2 templating, modern frontend frameworks做好的scaffolds, 還有更多~

4.3 Django

Django 也有自己的 bootstrap 工具, 內建在 django-admin 中.

Django 跟 Pyramid 區別在於: Django 由多個應用程式組成一個專案, 而 Pyramid 以及 Flask 專案是包含 View 和 Model 單一應用程式 . 理論上, Flask 和 Pyramid 的專案允許存在多個 project/app, 不過在預設配置中只能有一個.

Django 預設只在專案中建立 空白的 model 和模板檔案, 供新手參考的示範程式碼不多. 此外, 開發者在釋出應用程式的時候, 還要自己配置, 這也是個麻煩.

bootstrap 工具的缺點是沒有指導開發者如何打包應用. 對於那些沒有經驗的新手來說, 第一次部署應用將是個很頭疼的問題. 像 django-oscar 這樣的大社群, 專案都是打包好了, 放在 PyPi 上供大家安裝. 但是 Github 上面的小專案缺少統一的打包方式.

5 模板

一個Python應用能夠響應HTTP請求將是一個偉大的開端,但是有可能你的大多數使用者是沒有興趣使用curl與你的web應用互動的。幸運的是,這三個競爭者提供了使用自定義資訊填充HTML的方法,以便讓大夥們能夠享受時髦的Bootstrap 前端。

模板讓你能夠直接向頁面注入動態資訊,而不是採用AJAX。你只需要一次請求就可以獲取整個頁面以及所有的動態資料,這對使用者體驗來說是很好的。這對於手機網站來說尤其重要,因為一次請求花費的時間會更長。

所有的模板選項依賴於“上下文環境(context)”,其為模板轉換為HTML提供了動態資訊。模板的最簡單的例子是填充已登入使用者的名字以正確的迎接他們。也可以用AJAX獲取這種動態資訊,但是用一整個呼叫來填寫使用者的名字有點過頭了,而同時模板又是這麼的簡單。

5.1 Django

我們使用的例子正如寫的那麼簡單,假設我們有一個包含了使用者名稱的funllname屬性的user物件。在Python中我們這樣向模板中傳遞當前使用者:

擁有這個模板的上下文很簡單,傳入一個Python物件的字典和模板使用的資料結構。現在我們需要在頁面上渲染他們的名字,以防頁面忘了他們是誰。

首先,你會注意到這個 {% if user %} 概念。在Django模板中, {% 用來控制迴圈和條件的宣告。這裡的if user宣告是為了防止那些不是使用者的情況。匿名使用者不應該在頁面頭部看到“你已經登入”的字樣。

在if塊內,你可以看到,包含名字非常的簡單,只要用{{}}包含著我們要插入的屬性就可以了。{{是用來向模板插入真實值的,如{{ user.fullname }}。

模板的另一個常用情況是展示一組物品,如一個電子商務網站的存貨清單頁面。

在模板中,我們使用同樣的{%來迴圈清單中的所有條目,並填入它們各自的頁面地址。

為了做大部分常見的模板任務,Django可以僅僅使用很少的結構來完成目標,因此很容易上手。

5.2 Flask

Flask預設使用受Django啟發的Jinja2模板語言,但也可以配置來使用另一門語言。不應該抱怨一個倉促的程式設計師分不清Django和Jinja模板。事實是,上面的Django例子在Jinja2也有效。為了不去重複相同的例子,我們來看下Jinja2比Django模板更具表現力的地方。

Jinja和Django模板都提夠了過濾的特性,即傳入的列表會在展示前通過一個函式。一個擁有博文類別屬性的部落格,可以利用過濾特性,在一個用逗號分割的列表中展示博文的類別。

在Jinja模板語言中,可以向過濾器傳入任意數量的引數,因為Jinja把它看成是 使用括號包含引數的Python函式的一個呼叫。Django使用冒號來分割過濾器的名字和過濾引數,這限制了引數的數目只能為一。

Jinjia和Django的for迴圈有點類似。我們來看看他們的不同。在Jinjia2中,for-else-endfor結構能遍歷一個列表,同時也處理了沒有項的情況。

Django版的這個功能是一樣的,但是是用for-empty-endfor而不是for-else-endfor。

除了語法上的不同,Jinja2通過執行環境和高階特性提供了更多的控制。例如,它可以關閉危險的特性以安全的執行不受信任的模板,或者提前編譯模板以確保它們的合法性。

5.3 Pyramid

與Flask類似,Pyramid支援多種模板語言(包括Jinja2和Mako),但是預設只附帶一個。Pyramid使用Chameleon,一個 ZPT (Zope Page Template) 模板語言的實現。我們來回頭看看第一個例子,新增使用者的名字到網站的頂欄。Python程式碼除了明確呼叫了render_template函式外其他看起來都差不多。

但是我們的模板看起來有些不同。ZPT是一個基於XML得模板標準,所以我們使用了類XSLT語句來運算元據。

Chameleon對於模板操作有三種不同的名稱空間。TAL(模板屬性語言)提供了基本的條件語句,字串的格式化,以及填充標籤內容。上面的例子只用了TAL來完成相關工作。對於更多高階任務,就需要TALES和METAL。TALES( 模板屬性表示式語法的語言)提供了像高階字串格式化,Python表示式評估,以及匯入表示式和模板的表示式。

METAL(巨集擴充套件模板屬性語言)是Chameleon模板最強大的(和複雜的)一部分。巨集是可擴充套件的,並能被定義為帶有槽且當巨集被呼叫時可以被填充。

6. 利用框架行動起來

對於各個框架,我們將通過製作一個叫做wut4lunch的應用來了解,這個應用是告訴整個網際網路你午飯吃了什麼的社交網路。很自由的一個起始想法,完全可以隨意改變。應用將有一個簡單的介面,允許使用者提交他們午飯的內容,並看到其他使用者吃的什麼的列表。主頁完成後將看起來像這樣。

cpp_py_medium

6.1 使用Flask的Demo應用

最短的實現用了34行Python程式碼和一個22行的Jinja模板。首先,我們有些管理類的任務要做,比如初始化我們的應用並拉近我們的ORM。

現在我們看下我們的模型,這將和另兩個樣例基本一樣。

哇,相當簡單。最難的部分是找到合適的 SQLAlchemy資料型別,選擇資料庫中String域的長度。使用我們的模型也超級簡單,這在於我們將要看到 SQLAlchemy查詢語法
構建我們的提交表單也很簡單。在引入Flask-WTForms和正確的域型別後,你可以看到表單看起來有點像我們的模型。主要的區別在於新的提交按鈕和食物與提交者姓名域的提示。

應用中的SECRET_KEY域是被WTForms用來建立CSRF符號的。它也被itsdangerous(Flask內包含)用來設定cookies和其他資料。

讓表單在瀏覽器中顯示意味著模板要有它。我們像下面那樣傳遞進去。

好了,發生了什麼?我們得到已經用Lunch.query.all()提交的午餐列表,並例項化一個表單,讓使用者提交他們自己的美食之旅。為了簡化,變數使用相同的名字出入模板,但這不是必須的。

這就是模板的真實情況,我們在已經吃過的午餐中迴圈,並在<ul>中展示他們。這幾乎與我們前面看到的迴圈例子一樣。

模板的<form>部分僅僅渲染我們在root()檢視中傳入模板的WTForm物件的表單標籤和輸入。當表單提交時,它將向/new提交一個POST請求,這個請求會被下面的函式處理。

在驗證了表單資料後,我們把內容放入我們Model物件中,並提交到資料庫。一旦我們在資料庫中存了午餐,它將在人們吃過的午餐列表中出現。

最後,我們只需做(非常)少量的工作來讓應用執行起來。使用SQLAlchemy,我們可以建立儲存午餐的表,然後開始執行我們寫的路徑管理就行了。

6.2 測試Django版APP

Django版wut4lunch 和Flask版有點像,但是在Django專案中被分到了好幾個檔案中。首先,我們看看最相似的部分:資料庫模型。它和SQLAlchemy版本的唯一不同之處是宣告儲存文字的資料庫欄位有輕微的語法區別。

在表單系統上。不像Flask,我們可以用Django內建的表單系統。它看起來非常像我們在Flask中使用的WTFroms模組,只是語法有點不同。

現在我們只需要構造一個LunchForm例項傳遞到我們的模板。

render函式是Django shortcut,以接受請求、模板路徑和一個上下文的dict。與Flask的render_template類似,它也接受接入請求。

儲存表單應答到資料庫是不一樣的,Django呼叫模型的 .save()方法以及處理會話管理而不是用全域性資料庫會話。乾淨利落!

Django提供了一些優雅的特性,讓我們管理使用者提交的午餐,因此我們可以刪除那些不合適的午餐資訊。Flask和Pyramid沒有自動提供這些功能,而在建立一個Django應用時不需要寫另一個管理頁面當然也是其一個特性。開發者的時間可不免費啊!我們所要做的就是告訴Django-admin我們的模型,是在wut5lunch/admin.py中新增兩行。

Bam。現在我們可以新增刪除一些條目,而無需額外的工作。

最後,讓我們看下主頁模板的不同之處。

Django擁有方便的快捷方式,在你的頁面中引用其他的檢視。url標籤可以使你重建應用中的URLs,而不需破壞檢視。這個是因為url標籤會主動查詢檢視中的URL。

表單被不同的語法渲染,我們需要人工在表單主體中新增CSRF token,但這些區別更多的是裝飾

6.3測試Pyramid版App

最後,我們看看用Pyramid實現的同樣的程式。與Django和Flask的最大不同是模板。只需要對Jinja2做很小的改動就足以解決我們在Django中的問題。這次不是這樣的,Pyramid的Chameleon模板的語法更容易讓人聯想到XSLT而不是別的。

與Django模板類似,缺少for-else-endfor結構使得邏輯稍微的更清晰了。這種情況下,我們以if-for 和 if-not-for 語句塊結尾以提供同樣的功能。使用{{或{%來控制結構和條件的Django以及AngularJS型別的模板讓使用XHTML標籤的模板顯得很外行。

Chameleon模板型別的一大好處是你所選擇的編輯器可以正確的使語法高亮,因為模板是有些得XHTML。對於Django和Flask模板來說,你的編輯器需要能夠正確的支援這些模板語言高亮顯示。

Pyramid中表單得轉換稍微更細緻些,因為pytamid_simpleform不像Django表單的form.as_ul函式那樣可以自動轉換所有的表單欄位。

現在我們看看什麼返回給應用。首先,定義我們需要得表單並呈現我們的主頁。

獲取午餐的查詢語法和Flask的很相似,這是因為這兩個demo應用使用了流行的SQLAlchemy ORM來提供持久儲存。在Pyramid中,允許你直接返回模板上下文的字典,而不是要呼叫特殊的render函式。@view_config裝飾器自動將返回的上下文傳入要渲染的模板。避免呼叫render方法使得Pyramid寫的函式更加容易測試,因為它們返回的資料沒有被模板渲染物件掩蓋。

從Pyramid的請求物件中更加容易得到表單資料,因為在我們獲取時會自動將表單POST資料解析成dict。為了阻止同一時間多併發的請求資料庫,ZopeTransactions模組提供了上下文管理器,對寫入邏輯事物的資料庫進行分組,並阻止應用的執行緒在各個改變時互相影響,這在你的檢視共享一個全域性session並接收到大量通訊的情況下將會是個問題。

7. 總結

Pyramid是三個中最靈活的。它可以用於小的應用,正如我們所見,但它也支撐著有名的網站如Dropbox。開源社群如Fedora選擇它開發應用,如他們社群中的徽章系統,從專案工具中接受事件的資訊,並向使用者獎勵成就型別的徽章。對於Pyramid的一個最常見的抱怨是,它提供了這麼多的選項,以至於用它開始一個新專案很嚇人。

目前最流行的框架是Django,使用它的網站列表也令人印象深刻。Bitbucket,Pinterest,Instagram,以及Onion完全或部分使用Django。對於有常見需求的網站,Django是非常理智的選擇,也因此它成為中大型網站應用的流行選擇。

Flask對於那些開發小專案、需要快速製作一個簡單的Python支撐的網站的開發者很有用。它提供小型的統一工具,或者在已有的API上構建的簡單網路介面。可以快速開發需要簡單web介面並不怎麼配置的後端專案使用Flask將會在前端獲益,如jitviewer提供了一個web介面來檢測PyPy just-in-time的編譯日誌。

這三個框架都能解決我們簡單的需求,我們已經看到了它們的不同。這些區別不僅僅是裝飾性的,它們將會改變你設計產品的方法,以及新增新特性和修復的速度。因為我們的例子很小,我們看到Flask的閃光點,以及Django在小規模應用上的笨重。Pyramid的靈活並未體現出來,因為我們的要求是一樣的,但在真實場景中,新的需求會常常出現。

7.1 致謝

標題影像的logo來自與Flask、Django和Pyramid專案網站。

這篇文章非常感謝它的評閱者,Remy DeCausemaker,Ross Delinger和Liam Middlebrook,忍受了許多初期的草稿。

這篇文章的當前樣式來自於Adam Chainz、bendwarn、Serger Maertens、Tom Leo和wichert的評論和修正(名字按字母表順序)。

相關文章