作者:杜志鵬
鏈接:https://www.zhihu.com/question/31748237/answer/55313054
來源:知乎
著作權歸作者所有,轉載請聯系作者獲得授權。
分白話版和專業版 :)
白話版
恩,理解「藍本」對于沒有接觸其他Web編程實現的Python+Flask新手而言,是一個不好邁過的坎兒。
我得用「比喻」的方式穿插講述一下「藍本」,方便你更好理解,問這個問題估計是新手,所以我啰嗦點好了。
首先,提一下概念,「藍本」的對應官方詞匯是「Blueprint」,中譯還有「藍圖」這種叫法,是一種東西。
你去餐館吃飯,點一些菜,你一定吃過「麻辣香鍋」這種東西。你可以點一些雜七雜八的菜,選好口味,之后一個碗兒端上來,你吃的很滋味,并且也會覺得這種「一鍋端」的方式很好,畢竟不會一道吃完等下一道啊,一起上來熱乎乎的多好。并且吃的很少的時候,一點點的菜單獨用一個碗兒裝也是有點「裝逼」的事情。
假如你一個人一己之力要寫一個BBS(論壇系統)的話,上面的這個例子,其真實寫照是初期功能很弱時,可能頁面不多,實現的功能也很少。可能首頁就是一個帖子列表,允許用戶發布帖子,并且不涉及登錄退出、會員等級、權限管理等,你想著,那是后面再說的事情。于是,你將網站主干代碼寫在一個run.py文件里,讓網站成功的跑了起來,你覺得這樣實現很方便,快捷。
如果我讓你這個時候把run.py理一下,能獨立出來的獨立成其他的文件,之后在run.py里導入,你要罵我「裝逼」了,沒事找事。是的,「藍本」并不適合用來構建想當簡單的網站系統。
然而,你胃口變大了,你也有了好朋友,你們要一起去吃飯。于是你遇到一些問題:
你的朋友不喜歡吃某些東西,但你喜歡吃。你朋友喜歡吃的一些東西,你不喜歡吃。
人多了,點的東西多了,你們想如果吃不完可以打包明天吃,但是你們打包還是要挑一些菜打包的,畢竟有些菜隔夜就味道不好,容易壞掉。
你和你的朋友都想先吃一種東西,然后再吃一種東西。比如先吃點葷的,然后吃點素的解解葷。
以上,如果你們還去吃「麻辣香鍋」,就都不開心了,你想想看。
類似的,當你的BBS系統越來越復雜,有了登錄退出、會員等級、權限管理等等功能后,問題迎面而來:
run.py文件肯定越來越大,代碼行數越來越多。你網站所有的路由寫在一個文件里,功能也在一個文件里,很容易出現錯誤,并且難以定位。當然,你如果沒實戰經驗,確實不能切身體會這點。那么我說另一個顯而易見的問題:import(導入)和config(配置)一般寫在頭部,你的新路由按順序怎么也得寫在幾百行開外的地方,于是你編程時一下看代碼文件最上面,一下拖到底部繼續寫,這就夠「操蛋」的了。
大型網站一定是多人編程,如果此刻安排你做登錄退出功能,你的朋友做權限管理功能,那么老板當然不容忍你干完你朋友才開工,一起開干啊!可是此時只有run.py一個文件,沒錯,你和你的基友可以各自拷貝一份run.py各自開始寫,結果你的代碼從第100行開始寫,你的朋友也從100行開始寫,然后你們還各自在頭部import了一些新東西。等寫好了,怎么辦?線上就一個run.py,這下「糟糕」了,苦苦「合并」代碼嗎?這更「操蛋」了。如果10個人寫網站呢?——「蛋蛋艸碎了」
所以,你此刻第一反應當然是「麻辣香鍋」吃不成了,這「一碗菜是一碗菜」的方式是最佳的解決方案,愛吃什么吃什么,愛打包哪碗直接裝起來,不用拿著筷子挑。
當你利用Python+Flask構建一個網站時,人為的提煉一些代碼獨立成文件是「不妥」的方式,你需要Flask本身給予一些支持,于是「藍本」出現了。
「藍本」允許你將不同路由分開,提供一些「規范(標準)」,并且附帶了很多好處。你可以要求商家不同的菜上來裝不同的盤子,就像你也可以要求「藍本」針對不同路由應用不同靜態資源,導致不同的URL出現不一樣的網站界面(否則前端CSS就混雜了)。
「藍本」使得你和基友一起工作時不會再有上面的麻煩事,你改你的A文件,他改他的B文件,回頭各自提交各自的,多好。
「藍本」讓你做另一個程序時,如果那個程序也有登錄退出,理想情況下你甚至可以直接將BBS負責登錄退出的部分直接挪過去用,這遠比直接從一個run.py里復制出代碼現實的多,要知道即使一個登錄退出的功能,你在一個run.py里實現,代碼會散落在各處的。
「藍本」還有其他好處,其實本質上來說就是讓程序更加松耦合,更加靈活,增加復用性,提高查錯效率,降低出錯概率。
專業版
正常情況下,入門Flask框架都是從寫一個單文件,然后看到頁面顯示 Hello,World! 開始的。這個單文件一般命名hello.py或run.py。
你會在單文件中寫一些路由,比如首頁的、列表頁的、詳情頁的,就像我博客這么簡單的頁面結構,完全可以一個文件搞定。
# -*- coding: utf-8 -*-# run.pyfromflaskimportFlask,render_template,requestfromflask_flatpagesimportFlatPagesFLATPAGES_AUTO_RELOAD=TrueFLATPAGES_ROOT='pages'FLATPAGES_EXTENSION='.md'app=Flask(__name__)app.config.from_object(__name__)flatpages=FlatPages(app)@app.route('/')defindex():returnrender_template('index.html')@app.route('/list//')defpage_list(tag):articles=(pforpinflatpagesiftaginp['tags'])pages=sorted(articles,reverse=True,key=lambdap:p.meta['date'])returnrender_template('list.html',tag=tag,pages=pages)@app.route('/page//')defpage(uri):p=flatpages.get_or_404(uri)returnrender_template('page.html',p=p)if__name__=='__main__':app.run()
上面是一個可以運行的微型博客的全部后端程序,幾乎就是你現在正常訪問的博客的全部,你看,非常簡短!
如果你用過類似WordPress或者新浪博客等博客工具,顯然有一個問題:哪兒可以添加新的文章和管理文章分類?的確,這段代碼離一個傳統的博客程序該有的全貌相差甚遠,因為還欠缺一個管理后臺。
以WordPress為例子,在一般情況下,如果訪問如http://xxx.com,可以看到博客的前臺,也就是各種博客文章。而訪問如http://xxx.com/wp-admin則可以進入到后臺,用來管理文章和發布新文章。在路由的寫法上,對于上面這個文件你可能要這樣擴充一下功能(為了節省篇幅方便演示,下面代碼詳細部分省略,以 pass 代替詳細部分)。
# -*- coding: utf-8 -*-# run.pyfrom flask import Flask, render_template, requestfrom flask_flatpages import FlatPagesFLATPAGES_AUTO_RELOAD = TrueFLATPAGES_ROOT = 'pages'FLATPAGES_EXTENSION = '.md'app = Flask(__name__)app.config.from_object(__name__)flatpages = FlatPages(app)@app.route('/')def index():pass? # 為了方便演示這里省略詳細代碼@app.route('/list//')def page_list(tag):pass@app.route('/page//')
def page(uri):
pass
# 新增的后臺部分代碼
from adminPages import admin
app.register_blueprint(admin, url_prefix='/admin')
if __name__ == '__main__':
app.run()
上述代碼新增加了管理后臺部分,具體是增加了一個后臺登錄界面、文章列表以及新增和編輯文章的功能共四個部分。
繼續上述思路,可以想到新的幾個問題:
1)一般情況下,前臺和后臺用兩套模板。或者通俗的講,前臺費力弄得好看點,后臺反正自己用,能用就成,丑點無所謂。那么怎么讓前臺和后臺用兩套模板?
2)后臺部分邏輯比前臺復雜,還需要導入新的包,如果和前臺寫在一個文件里,后面修改會不會容易出錯,例如本來改后臺部分結果牽連前臺出問題?
3)既然Python力求簡潔,那代碼能否再簡潔些?比如新增的路由參數 /admin 重復寫了4遍,能不能對后臺定義一個前綴,后臺部分的自動加這個/admin ?
4)如果這個博客程序需要多人來維護,多人編輯同一個文件去提交時沖突如何解決?
亦或者你還有其他的一些疑問,如果是關乎怎么把程序做大的同時,還能保證簡潔、良好、易于維護的代碼組織結構,一個容易想到的方向就是程序的「模塊化」。
在你學習Python的過程中,早已見識到了Python模塊化,比如Python的包概念,也即你總是能看到的 import …… 引入的那些東西和本身的這個引入方法。那么,對于Flask而言,你也許會舉一反三的把博客的后臺程序部分抽離出來,單獨編寫一個文件,然后再 import ……? ,這樣不就模塊化了?!
但是等等,程序跑不起來了?還有,前面剛提到過的四個問題,似乎這樣做之后只是勉強解決了第四個問題,前三個呢?
所以,可想而知,我們 的確需要一個模塊化的方法,這個思路的方向本身不錯,要點在于我們需要的是一個Flask認可的、兼容的、支持的模塊化方法。Flask的作者作為一個老江湖早就考慮到這點,并提供了一個名為Blueprint的方法來讓你實現模塊化組織程序結構。
Blueprint中文的翻譯現在有兩種常見的:藍圖以及藍本。這里我們稱藍圖好了。
對于上述程序,我們應用藍圖方法修改之后,大致是下面這樣了。
首先,我們新增一個文件adminPages.py,如下:
# -*- coding: utf-8 -*-# adminPages.pyfromflaskimportBlueprintadmin=Blueprint('admin',__name__)@admin.route('/login/')defadmin_login():pass@admin.route('/page/')defadmin_pages():pass@admin.route('/page/new/')defnew_page():pass@admin.route('/page/edit/')defedit_page():pass
同時,改寫run.py 如下:
# -*- coding: utf-8 -*-# run.pyfromflaskimportFlask,render_template,requestfromflask_flatpagesimportFlatPagesFLATPAGES_AUTO_RELOAD=TrueFLATPAGES_ROOT='pages'FLATPAGES_EXTENSION='.md'app=Flask(__name__)app.config.from_object(__name__)flatpages=FlatPages(app)@app.route('/')defindex():pass# 為了方便演示這里省略詳細代碼@app.route('/list//')defpage_list(tag):pass@app.route('/page//')defpage(uri):pass# 新增的后臺部分代碼fromadminPagesimportadminapp.register_blueprint(admin,url_prefix='/admin')if__name__=='__main__':app.run()
好,這就完成了一個應用藍圖的簡單例子。讓我們再來回顧上述代碼,看看都做了些什么。
首先,我們新增了一個adminPages.py的文件,并在文件中導入了Flask的藍圖方法Blueprint。
緊接著,我們用admin實例化了一個藍圖應用,就是這句:
admin=Blueprint('admin',__name__)
Blueprint要求至少傳入兩個參數,分別是藍圖的名字和藍圖所在的包或模塊。
我們將后臺部分獨立出來作為藍圖模塊,并取名為 admin ,所以我們傳入第一個參數'admin'。
緊接著是所在的包或模塊,如果你有印象,大概還記得Python有一個特殊屬性_ name _ ,當用在一個文件里時,代表的是模塊(或包)的名字,所以第二個參數傳入 _ name _ 。
因為使用了藍圖,原來的路由 @app.route() 改寫成了 @admin.route(),改寫的原因你需要看一下官方文檔了解更為深入的細節,這里暫且不表。
對于run.py,我們剝離出了 admin 部分的程序代碼,但在文件末尾,又導入了藍圖模塊,然后按照Flask藍圖的用法去注冊了藍圖,就是這兩行代碼:
fromadminPagesimportadminapp.register_blueprint(admin,url_prefix='/admin')
其中,app.register_blueprint()的第一個參數無需過多解釋,第二個參數 url_prefix= 即在定義前綴。還記得前面的四個問題嗎?這里針對的就是第三個問題。再回過頭去看看,你會發現adminPages.py里,原來路由里的 /admin 都去掉了。
就是這些,我們簡單的應用了一把Flask的藍圖功能,一定程度上算是解決了前面四個問題中的三個,還有一個兩套模板的問題藍圖也支持,并且十分簡單,留給你去看看官方文檔了解吧。
提示:template_folder=
以上,我所說的針對「小白」新手,如果你有一定的實踐經驗,官方的文檔寫得更準確些:使用藍圖的模塊化應用
希望我的回答幫你繞好彎子。: P