這幾天想學(xué)新東西,就看了flask框架,本身對python不太了解,網(wǎng)上的很多教程看了,總是在某些地方卡住。翻到一本電子書《Flask web Development》,還不錯。但是說實話,英語比較渣,原文看起來很累,并且由于偷懶總習(xí)慣只看代碼,而不看作者的解釋。這樣學(xué)習(xí)沒有效率,第一遍很快只看代碼就完成了,程序也能跑起來。但只圖快的后果就是幾乎沒啥收獲,很多東西都只知其然。
為了避免偷懶,下決心每天翻譯一點,堅持仔細認(rèn)真學(xué)習(xí)。從第二章開始,第一張的安裝神馬的都跳過了。翻譯盡量卡著原字句走,只根據(jù)漢語習(xí)慣略作調(diào)整;有些專用語可能不準(zhǔn)確,另外盡量不看字典,只憑上下文猜不認(rèn)識的單詞——據(jù)說這也是背單詞手段之一:-)。所以必然會有錯誤之處,歡迎指正。
第二章 基本程序結(jié)構(gòu)
本章你將學(xué)到Flask程序的不同部分,你也將編寫并運行你的第一個Flask應(yīng)用。
初始化
所有的Flask程序必須創(chuàng)建一個"程序?qū)嵗?,Web服務(wù)器將自己接收到的所有客戶端請求轉(zhuǎn)發(fā)給這一對象,使用的是網(wǎng)頁服務(wù)網(wǎng)管接口協(xié)議(WSGI)。這一程序?qū)嵗且粋€Flask類的對象,通常通過如下代碼進行創(chuàng)建:
from flask import Flask
app = Flask(__name__)
Flask類初始化參數(shù)是程序的包或主模塊的名字。對大多數(shù)程序來說,Python的__name__
變量就是正確的值。
注意:
傳遞給Flask程序構(gòu)造函數(shù)的name參數(shù)往往給新開發(fā)人員造成困擾。Flask采用這個參數(shù)判定程序的根目錄,以便于以后在程序中引用和查找相關(guān)資源。
稍后,你將看到更多完整的程序初始化的例子,單對一個簡單程序來說已經(jīng)足夠了。
路由和視圖函數(shù)
像瀏覽器這樣的客戶端發(fā)送請求(request)給服務(wù)器,服務(wù)器按順序轉(zhuǎn)發(fā)給Flask程序?qū)嵗?。程序?qū)嵗枰缹γ總€URL(統(tǒng)一資源定位符,也就是你在地址欄里看到的那一大長串)
請求運行哪一部分的代碼,所以它保持著一個關(guān)于URL和Python函數(shù)的映射表。對URL和函數(shù)之間關(guān)聯(lián)進行控制的就稱之為路由(route)。
定義路由的最方便的方法是通過app.route裝飾器暴露|裝飾一個函數(shù),程序?qū)嵗蜁堰@一函數(shù)注冊為一個路由。下列代碼展示了如何使用裝飾器來聲明一個路由:
@app.route('/')
def index():
return '<h1>Hello World!</h1>`
注意:
*裝飾器(Decorator)是python語言的標(biāo)準(zhǔn)功能,它可以在不同方式下更改一個函數(shù)的行為。最常見的就是使用裝飾器指定一個函數(shù)作為某事件的處理器 *
上述示例代碼將index()函數(shù)注冊為程序根URL的處理控制器。在程序部署到服務(wù)器上并關(guān)聯(lián)www.example.com域名后,在瀏覽器訪問www.example.com時,將觸發(fā)程序的index()函數(shù)。該函數(shù)的返回值——稱之為響應(yīng)(response),就是客戶端接收到的內(nèi)容。如果客戶端是瀏覽器,該響應(yīng)就是展示用網(wǎng)頁形式給用戶的文檔。
像index()這樣的函數(shù)被稱為視圖函數(shù)(view functions)。視圖函數(shù)的返回響應(yīng)可以是簡單的html文本串,也可以是包含更復(fù)雜的表單——稍后可見。
注意:
響應(yīng)字符串嵌入在Python代碼中將導(dǎo)致代碼難以維護。這里只是簡單介紹響應(yīng)方式。你可以在第三章中學(xué)到如何正確生成響應(yīng)的方法。
如果注意下每天使用網(wǎng)絡(luò)服務(wù)的URL的形式,你可能會看到包含變量段。舉例來說:你的facebook個人信息頁面地址應(yīng)該是http://www.facebook.com/<yourname>
,你的名字就是變量部分。Flask通過路由裝飾器的特殊語法來支持這一類型的URL。下面的例子定義了一個擁有變量名稱的路由:
@app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s!</h1>' % name
小括號部分就是動態(tài)部分,符合靜態(tài)部分的URL請求會被映射到這一路由。調(diào)用視圖函數(shù)時,F(xiàn)lask就會把動態(tài)部分作為一個參數(shù)發(fā)給它。在上面的視圖函數(shù)里,這一參數(shù)被用來生成個性化的歡迎信息并作為一個響應(yīng)返回。
在路由中動態(tài)部分默認(rèn)是作為字符串處理,但也可以指定為某種類型。如:/user/<int:id>
將只匹配包含整數(shù)的id動態(tài)參數(shù)URL。Flask路由支持int,float和path。path類型看起來也是一個字符串但其中的斜杠并不被認(rèn)為是分隔符,而是動態(tài)參數(shù)的一部分,不再對其進行解釋。
啟動服務(wù)
程序?qū)嵗衦un方法來運行Flask集成開發(fā)服務(wù)器:
if __name__ == '__main__':
app.run(debug=True)
__name__=='__main__'
是python常見的方言,這里是用來保證僅僅在直接運行腳本的情況下啟動開發(fā)服務(wù)器。當(dāng)腳本被其他腳本導(dǎo)入時,它將假定調(diào)用它的父級腳本會運行另外的服務(wù)器,就跳過后面app.run()
。
一旦服務(wù)器啟動,它就進入循環(huán),等待請求并為之提供服務(wù)。該循環(huán)持續(xù)不斷直到程序結(jié)束,例如按下Ctrl+C。
還有基本參數(shù)選項可以指定給app.run()
來配置服務(wù)器操作模式。在開發(fā)模式下,服務(wù)器將啟動調(diào)試,這將激活debugger和reloader(調(diào)試模式和重載)
。你只需設(shè)置參數(shù)為debug=True
注意:
Flask提供的web服務(wù)器"不可用于生產(chǎn)環(huán)境"。你將在第七章學(xué)習(xí)如何設(shè)置生產(chǎn)web服務(wù)器。
完整的程序
在上一節(jié),你已經(jīng)學(xué)習(xí)了Flask的不同部分,現(xiàn)在是時候自己寫一個了。完整的hello.py腳本程序僅僅就是將我們在前面描述過的三部分內(nèi)容合并到一個文件中,如下:
Example 2-1. hello.py: A complete Flask application
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
if __name__ == '__main__':
app.run(debug=True)`
要運行這個程序,你需要確保先前你設(shè)置的虛擬開發(fā)環(huán)境(第一章設(shè)置 安裝python 后 pip install virtualenv)
已激活并且安裝了Flask?,F(xiàn)在打開你的web瀏覽器并在地址欄里輸入http://127.0.0.1:5000/
,如果正常的話,瀏覽器將顯示服務(wù)器的響應(yīng)——頁面出現(xiàn)“Hello World!”。當(dāng)然,運行服務(wù)器的話需要在命令行中輸入如下命令:
(venv)$python hello.py
*running on http://127.0.0.1:5000
*Restarting with realoader`
如果你輸了其他的URL,該程序就不知道應(yīng)該如何處理,它將返回一個404錯誤代碼給瀏覽器——這意味著你要訪問的頁面并不存在。
改進版本的程序如2-2示例所示,它添加了第二路由——包含動態(tài)參數(shù)。當(dāng)你訪問該URL時,將見到個性化的歡迎。
Example 2-2. hello.py: Flask application with a dynamic route
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
@app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s!</h1>' % name
if __name__ == '__main__':
app.run(debug=True)`
要測試該動態(tài)路由,啟動服務(wù)器后訪問http://localhost:5000/user/Dave
,程序?qū)⒁詡€性化的歡迎來響應(yīng),根據(jù)動態(tài)參數(shù)name生成歡迎信息。每當(dāng)嘗試不同的名字,你會看到根據(jù)名字生成歡迎頁面。
譯注:2016-7-20,現(xiàn)在全新安裝虛擬環(huán)境的話,F(xiàn)lask已經(jīng)升級到0.11.1了,會出現(xiàn)一些提醒信息,比如導(dǎo)入擴展的語法將要從
form flask.ext.moment import Moment
變更為from flask_moment import Moment
。當(dāng)然,這并不意味著是錯誤,程序仍舊可以照常運行的。這是為將來大版本語法變動的預(yù)防針:-)
請求-響應(yīng)循環(huán)
現(xiàn)在你已經(jīng)搞定一個基本的Flask程序了,你可能希望對Flask工作過程了解的更多。下面的小節(jié)描述了一些框架的設(shè)計模樣(?)。
應(yīng)用程序和請求上下文
當(dāng)Falsk接收到客戶端的請求,他需要激活一些視圖函數(shù)需要的對象來處理它們。最好的例子就是request對象,它封裝了所有的客戶端http請求。
最簡單的作法是將它作為一個參數(shù)發(fā)送,這樣Flask就能讓一個視圖函數(shù)訪問“請求對象”,但這也會導(dǎo)致每個單一視圖函數(shù)都要有可擴展的參數(shù):視圖函數(shù)要完成一個請求訪問,可能需要訪問不僅是請求對象自己。考慮到這些,情況就更復(fù)雜了。
為了避免雜亂的視圖函數(shù)帶著不一定需要的大量參數(shù),F(xiàn)lask使用上下文(contexts),來暫時為特定對象提供被全局訪問的可能。多虧了上下文,視圖函數(shù)就可以如下書寫:
from flask import request
@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return '<p>Your browser is %s</p>' % user_agent
注意,在這個視圖函數(shù)中request就像一個全局變量一樣被使用。實際上,你仔細考慮下就會發(fā)現(xiàn),request不可能是一個全局變量——在一個多線程服務(wù)器上,在同一時間線程工作在不同客戶端請求上,所以每個線程需要看到不同的——自己特定的那個request對象。上下文就使Flask的這一功能實現(xiàn),某一特定變量只對一個線程可見而其他的則看不到。
注意:
線程 是可以獨立管理的最小指令序列。通常,一個進程(process)擁有多個活動的線程,他們可以共享資源如內(nèi)存或文件句柄。多線程web服務(wù)器會啟動一個線程池,每當(dāng)進來一個請求就從線程池里調(diào)起一個線程來進行處理。
在Flask中有兩個上下文:程序上下文和請求上下文。下表展示了每個上下文擁有的變量。
*變量名* *上下文* *描述*
current_app 程序上下文 當(dāng)前活動的程序?qū)嵗?
g 程序上下文 對象程序可以請求發(fā)起是用來臨時存儲數(shù)據(jù),每個請求發(fā)起都會重置。
request 請求上下文 請求對象本身,封裝了客戶端發(fā)來的http請求內(nèi)容
session 請求上下文 用戶會話,一個字典,程序可以用來在各個請求之間記住存儲的值。
在分發(fā)請求或處理后移除請求前,flask激活(或推出)程序上下文和請求上下文。當(dāng)程序上下文被推出,current_app和g 變量對線程來說就生效了,同樣,當(dāng)請求上下文被推出,request和session也將生效。如果激活沒有活動程序或請求上下文就訪問這些變量,將導(dǎo)致報錯。如果你還不太理解他們?yōu)楹芜@么有用,不需要擔(dān)心,后面我們將詳細了解
下列python shell會話模擬展示了程序上下文是如何工作的:
>>> from hello import app
>>> from flask import current_app
>>> current_app.name
Traceback (most recent call last):
...
RuntimeError: working outside of application context
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> current_app.name
'hello'
>>> app_ctx.pop()
在這里,如果沒有程序激活則current_app.name失敗了,相反上下文被推出則有效。注意,在程序?qū)嵗鲜侨绾瓮ㄟ^app.app_context()來調(diào)用程序上下文的。
請求分發(fā)
當(dāng)程序接收到客戶端請求時,它需要查找視圖函數(shù)來為之服務(wù)。在這任務(wù)里,F(xiàn)lask在URL映射中(包含著URL和對應(yīng)視圖函數(shù)的映射)
查找請求的URL來處理之。Flask通過app.route裝飾器(或等價的非裝飾器版本:app.add_url_rule()函數(shù))
來創(chuàng)建這一映射。
Url映射是啥樣的呢,你可以檢查在python shell 中hello.py創(chuàng)建的這個映射。首先確保你的虛擬安裝環(huán)境激活:
(venv) $ python
>>> from hello import app
>>> app.url_map
Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])
‘/’和/user/<name>路由是在程序中通過app.route裝飾器定義的。/static/<filename>路由則是Flask添加用來訪問靜態(tài)文件的。你將在第三張學(xué)到更多的關(guān)于靜態(tài)文件的東西。
在URL映射中出現(xiàn)的HEAD,OPTIONS,GET元素是路由控制處理的“請求方法”。Flask給每個路由附加上方法,這樣不同的請求方法就會被發(fā)送到同一URL但被不同的視圖函數(shù)處理。HEAD和OPTIONS方法由Flask自動管理,所以實際上也可以說本程序的三個路由在URL映射中添加了GET方法。
在第四章你將學(xué)習(xí)路由不同請求之間的差異。
請求鉤子
有些時候,需要在請求被執(zhí)行之前或之后執(zhí)行某些代碼,鉤子就派上了用場。舉例來說,在每個請求的開始部分,可能需要創(chuàng)建一個數(shù)據(jù)庫連接,或者許可用戶執(zhí)行該請求。Flask提供了選項來注冊一個通用函數(shù),以便于在一個請求被分派到視圖函數(shù)之前或之后來調(diào)用,這可以避免在每個視圖函數(shù)中重復(fù)復(fù)制代碼來實現(xiàn)其功能。
請求鉤子以裝飾器的形式來實現(xiàn)。Flask支持四種鉤子:
before_first_request: 在處理第一個請求之前執(zhí)行該注冊函數(shù)
before_request: 在每個請求前都執(zhí)行該注冊函數(shù)
after_request: 在每個請求之后都執(zhí)行該注冊函數(shù),如果沒有未被處理的錯誤的話。
teardown_request: 在每個請求之后都執(zhí)行該注冊函數(shù),即使有未被處理的錯誤
為了在請求鉤子和視圖函數(shù)之間共享數(shù)據(jù),一個常用方案就是使用全局上下文g。舉例來說,before_request句柄能從數(shù)據(jù)庫中加載已登錄的用戶,并將之存儲在g.user對象。然后,如果調(diào)用視圖函數(shù),他就可以從這訪問用戶信息。
在下面章節(jié)中將展示請求鉤子,所以即使現(xiàn)在沒有啥感覺也不需擔(dān)心。
響應(yīng)
當(dāng)Flask調(diào)用視圖函數(shù),它期待該函數(shù)將返回值作為對請求的回應(yīng)。在大部分情況下,響應(yīng)是一個簡單的字符串被發(fā)送給客戶端呈現(xiàn)為HTML頁面。
但HTTP協(xié)議要求不僅僅是字符串作為響應(yīng)。HTTP響應(yīng)一個非常重要的部分就是響應(yīng)狀態(tài)代碼,flask通常默認(rèn)設(shè)置為“200”,它代表請求被成功回復(fù)。
當(dāng)視圖函數(shù)需要用不同的狀態(tài)代碼回復(fù)響應(yīng)時,它就會把一個數(shù)值添加在相應(yīng)文本后,作為第二個返回值。舉例來說,下列視圖函數(shù)將返回狀態(tài)碼400,它意味著請求錯誤:
@app.route('/')
def index():
return '<h1>Bad Request</h1>', 400
視圖函數(shù)返回的響應(yīng)也可以攜帶第三個參數(shù)——添加在一個http響應(yīng)上的字典頭部。這是比較少見的,但你將在第14章看到示例。
作為返回一個、兩個、甚至三個值的元組的替代方案,F(xiàn)lask視圖函數(shù)還可以選擇返回一個對象(response對象)。make_response()函數(shù)可以取1、2或3個參數(shù),這些值被作為視圖函數(shù)的響應(yīng)統(tǒng)一作為一個對象response返回。有時候,這很有用,他可以在視圖函數(shù)內(nèi)部進行轉(zhuǎn)換然后使用響應(yīng)對象的方法深度配置響應(yīng)。下面是一個創(chuàng)建響應(yīng)對象并在其中設(shè)置cookie的例子:
from flask import make_response
@app.route('/')
def index():
response = make_response('<h1>This document carries a cookie!</h1>')
response.set_cookie('answer', '42')
return response
這里出現(xiàn)了一個特殊響應(yīng)類型叫redirect。這個響應(yīng)并不包含頁面文檔,它只是給瀏覽器一個新的URL地址以供瀏覽器加載新頁面。在第四章你將學(xué)到重定向通常和web表單同用。
一個重定向通常以302響應(yīng)代碼加上location頭部的URL地址來聲明。重定向響應(yīng)可以使用三個返回值或者一個response對象來生成,但為了便于使用,F(xiàn)lask提供了redirect()函數(shù)住手來創(chuàng)建。
from flask import redirect
@app.route('/')
def index():
return redirect('http://www.example.com')
其他的特殊響應(yīng)是abort函數(shù),他被用來處理錯誤。下面的代碼展示了當(dāng)傳遞給URL的id參數(shù)沒有匹配的用戶是返回一個404狀態(tài)碼:
from flask import abort
@app.route('/user/<id>')
def get_user(id):
user = load_user(id)
if not user:
abort(404)
return '<h1>Hello, %s</h1>' % user.name
注意:abort并不是將控制交還給調(diào)用它的函數(shù),而是以跳出一個錯誤的方式將控制轉(zhuǎn)交給了web服務(wù)器。
Flask擴展
Flask本身被設(shè)計成可擴展的。他特意空出一些重要部分,諸如數(shù)據(jù)庫,用戶認(rèn)證,這樣便于你自由選擇最適合那你程序的包或者自行編寫。
社區(qū)創(chuàng)建了針對各種要求的多種擴展,如果不夠還是有任意的標(biāo)準(zhǔn)python包或庫可供選擇使用。為了展示如何在程序中使用擴展,下面小節(jié)在你hello.py中添加了一個擴展以使程序能夠處理命令行參數(shù)。
通過Flask-script實現(xiàn)命令行選項
Flask的開發(fā)服務(wù)器支持啟動配置項,但唯一的方法就是在教程中給app.run()傳遞指定的參數(shù)。這非常不便,理想方法是公共命令行參數(shù)傳遞配置項。
Flask-Script就是這樣一個給你的Flask程序添加命令行參數(shù)的擴展。他支持通常選項配置也支持自定義命令。
這個擴展可以通過pip安裝:
(venv)$pip install flask-script
2-3例子展示了在hello.py中添加命令行解釋器所需要進行的改動。
example2-3: hello.py:使用Flask-script
from flask.ext.script import Manager
manager = Manager(app)
# ...
if __name__ == '__main__':
manager.run()
擴展是Flask特殊種類在flask.exe命名空間下暴露。Flask-script輸出一個名為Manager的類,可以從flask.ext.script中導(dǎo)入。
這個擴展的初始化方法和其他擴展類似:就愛能夠應(yīng)用程序示例作為參數(shù)傳遞給它的構(gòu)造函數(shù),從而生成一個類實例。該對象可以隨后被其他擴展使用。在本例中,通過manager.run()路由進行服務(wù)器啟動,同樣可以由此分析解釋命令行。
通過這些修改,程序要求一個基本的命令行選項配置參數(shù)?,F(xiàn)在運行一下hello.py看看其可用的信息:
$ python hello.py
usage: hello.py [-h] {shell,runserver} ...
positional arguments:
{shell,runserver}
shell Runs a Python shell inside Flask application context.
runserver Runs the Flask development server i.e. app.run()
optional arguments:
-h, --help show this help message and exit`
shell命令可以用來在程序上下文中啟動一個python shell 會話。你可以使用這個會話進行維護任務(wù)或者測試,調(diào)試問題。
runserver命令,就像其字面意思一樣,將啟動服務(wù)器:運行python hello.py runserver將 以調(diào)試模式啟動服務(wù)器,同樣,這里還有更多地可以選項:
(venv) $ python hello.py runserver --help
usage: hello.py runserver [-h] [-t HOST] [-p PORT] [--threaded]
[--processes PROCESSES] [--passthrough-errors] [-d]
[-r]
Runs the Flask development server i.e. app.run()
optional arguments:
-h, --help show this help message and exit
-t HOST, --host HOST
-p PORT, --port PORT
--threaded
--processes PROCESSES
--passthrough-errors
-d, --no-debug
-r, --no-reload`
--host 參數(shù) 非常有用,他告訴服務(wù)器監(jiān)聽哪個網(wǎng)絡(luò)接口來連接客戶端。默認(rèn)情況,F(xiàn)lask開發(fā)服務(wù)器監(jiān)聽localhost的連接,這時候只有來自于本機內(nèi)部的連接才會被接受。下列命令指定服務(wù)器鑒定在公用網(wǎng)絡(luò)接口上的連接,允許網(wǎng)絡(luò)中的其他計算機連接上來:
(venv) $ python hello.py runserver --host 0.0.0.0
* Running on http://0.0.0.0:5000/
* Restarting with reloader
web服務(wù)器現(xiàn)在允許網(wǎng)絡(luò)中的任意計算機訪問http://a.b.c.d:5000
, a.b.c.d是運行服務(wù)器計算機的擴展ip地址。
本章介紹了請求應(yīng)答得概念,但關(guān)于應(yīng)答內(nèi)容很多并沒有細談。Flask根據(jù)模板對生成相應(yīng)提供了很好的支持,他們非常重要我們將在后續(xù)章節(jié)中詳解。
第三章 模板