安裝
需要pip install flask
這是一個(gè)十分輕便的框架,開發(fā)迅速,成本上相比Django框架要少很多,而且十分靈活,可以完全按自己的需求來寫,但是功能上相比Django肯定也差一些,快速開發(fā)web服務(wù)器的時(shí)候這個(gè)用的還是很方便的
基本使用
flask框架只需要寫好需要返回的結(jié)果函數(shù)(視圖函數(shù)),然后在這些結(jié)果函數(shù)上面定義好對(duì)應(yīng)的每個(gè)路由(即網(wǎng)頁路徑)即可,然后通過訪問這些路由就是web的請(qǐng)求結(jié)果了
示例1
from flask import Flask
#實(shí)例化Flask對(duì)象,__name__有兩個(gè)功能:
#方便Flask框架尋找資源
#當(dāng)出錯(cuò)時(shí),方便尋找問題處
app = Flask(__name__)
#路由映射
@app.route('/')
def index():
return "<h1>hello<h1>"
#上面那個(gè)'/'就是一個(gè)路由,路徑是127.0.0.1:5000/,在這個(gè)路徑下就會(huì)執(zhí)行index函數(shù),結(jié)果頁面就展示hello
if __name__ == '__main__':
app.run(debug=True)
#啟動(dòng)web服務(wù),監(jiān)聽用戶請(qǐng)求,相當(dāng)于while True: Listen()
#debug默認(rèn)為False,此時(shí)一旦服務(wù)器出錯(cuò),只會(huì)在控制臺(tái)顯示錯(cuò)誤信息,而設(shè)置為True就能夠在頁面顯示錯(cuò)誤信息
#并且debug下如果服務(wù)器文件發(fā)生更改,也能夠檢測(cè)到,并修改頁面
默認(rèn)是5000端口,所以在http://127.0.0.1:5000/下可以看到
配置文件
前面的debug參數(shù)配置是寫在啟動(dòng)函數(shù)里的,但有時(shí)還是需要一個(gè)單獨(dú)的文件來作為配置文件,所以這里就介紹下通過引用外部文件來進(jìn)行配置的方法:
1.創(chuàng)建配置文件,里面寫參數(shù)配置(注意參數(shù)必須為大寫,比如在主程序里debug是小寫,但是到了配置文件里必須寫成DEBUG,否則沒效果)
2.在主程序?qū)肱渲梦募?br>
3.使用:app.config.from_object(配置文件),來導(dǎo)入配置
舉例:
配置文件b.py
DEBUG=True
主程序
from flask import Flask
import b
app = Flask(__name__)
app.config.from_object(b)
#導(dǎo)入配置
@app.route('/')
def index():
return "<h1>hel<h1>"
if __name__ == '__main__':
app.run()
結(jié)果可以發(fā)現(xiàn)和前面效果是一樣的,當(dāng)然這個(gè)是對(duì)于文件的配置,app.config是一個(gè)類似字典一樣的數(shù)據(jù)類型,如果只是希望配置簡(jiǎn)單的幾條數(shù)據(jù),可以直接在主程序里通過修改app.config這個(gè)字典來進(jìn)行配置,舉例:
from flask import Flask
import b
app = Flask(__name__)
app.config['DEBUG'] = True
#加入單個(gè)配置
@app.route('/')
def index():
return "<h1>hel<h1>"
if __name__ == '__main__':
app.run()
結(jié)果和前面的也是一樣的
路由配置
從上面的示例可以看出路由的配置就是通過裝飾器來完成的,這里再來個(gè)例子:
示例
from flask import Flask
app = Flask(__name__)
str1 = "aaa"
str2 = "bbb"
str3 = "ccc"
@app.route('/%s' % str1) #分別定義了三個(gè)路由
def index1():
return "{'name':'%s','age':20}" % str1
@app.route('/%s' % str2)
def index2():
return "{'name':'%s','age':20}" % str2
@app.route('/%s' % str3)
def index3():
return "{'name':{'%s', '%s', '%s'},'ages':{20, 30, 40} }" % (str1, str2, str3)
if __name__ == '__main__':
app.run(debug=True, host='192.168.17.1', port=6666)
可以看到上面定義了3個(gè)路由,每個(gè)路由返回的內(nèi)容都不一樣,于是我們用爬蟲分別訪問這三個(gè)路由,觀察結(jié)果:
import requests
res1 = requests.get("http://192.168.17.1:6666/aaa")
res2 = requests.get("http://192.168.17.1:6666/bbb")
res3 = requests.get("http://192.168.17.1:6666/ccc")
a = eval(res1.text)
print(a, type(a))
b = eval(res2.text)
print(b, type(b))
c = eval(res3.text)
print(c, type(c))
結(jié)果:
{'age': 20, 'name': 'aaa'} <class 'dict'>
{'age': 20, 'name': 'bbb'} <class 'dict'>
{'ages': {40, 20, 30}, 'name': {'aaa', 'bbb', 'ccc'}} <class 'dict'>
可以看出不同的路由返回了其對(duì)應(yīng)的請(qǐng)求結(jié)果,通過這個(gè)就可以很輕松的實(shí)現(xiàn)一些簡(jiǎn)易的小接口之類的了
動(dòng)態(tài)路由
前面的路由都是事先定義好的,而動(dòng)態(tài)路由的話,對(duì)方可以請(qǐng)求到?jīng)]有事先定義的路由,一個(gè)簡(jiǎn)單的方法可以通過<變量名>來獲取內(nèi)容,如果要控制該變量類型的話可以在前面加變量類型:,比如int型就:<int:id>,舉例:
web服務(wù)端
from flask import Flask
app = Flask(__name__)
@app.route('/abc/<int:id>')
def index2(id):
return "{'name':'%s','age':20}" % id
#路由路徑為/abc/數(shù)字
@app.route("/name=<name>&psd=<psd>")
def show(name, psd):
return "{'name':'%s', 'password': '%s'}" % (name, psd)
#路由路徑為/name=參數(shù),然后會(huì)獲取該參數(shù)并返回
if __name__ == "__main__":
app.run()
用爬蟲訪問發(fā)現(xiàn):
import requests
res1 = requests.get("http://127.0.0.1:5000/name=111&psd=222")
res2 = requests.get("http://180.212.38.57:6666/abc/1")
res3 = requests.get("http://180.212.38.57:6666/abc/a1")
print(res1.text)
print(res2.text)
print(res3.text)
結(jié)果:
{'name':'111', 'password': '222'}
{'name':'1','age':20}
<title>404 Not Found</title>
...
可以看出發(fā)送的請(qǐng)求1里,111和222被web服務(wù)端獲取了,并返回;請(qǐng)求2也正確訪問了;請(qǐng)求3因?yàn)閍bc后面不是數(shù)字,所以訪問錯(cuò)誤
獲取路由
前面都是通過路由來執(zhí)行對(duì)應(yīng)的函數(shù),現(xiàn)在我們可以通過函數(shù)來獲取對(duì)應(yīng)的路由,使用方法:
1.需要使用到flask下的url_for方法,from flask import url_for
2.在該方法里寫入要知道路由的函數(shù)名,參數(shù)可選
舉例:
from flask import Flask, url_for, redirect
app = Flask(__name__)
@app.route('/xxx')
def xxx():
return "hello"
@app.route('/yyy/<id>')
def yyy(id):
return id
@app.route('/get_url')
def main():
return url_for('xxx') + " " + url_for('yyy', id='zzz')
#返回執(zhí)行xxx函數(shù)對(duì)應(yīng)的路由,執(zhí)行yyy函數(shù),參數(shù)為zzz對(duì)應(yīng)的路由
@app.route('/visit_url')
def main2():
return redirect(url_for('xxx'))
#重定向訪問執(zhí)行xxx函數(shù)的路由
if __name__ == '__main__':
app.run(debug=True)
訪問http://127.0.0.1:5000/get_url和http://127.0.0.1:5000/visit_url結(jié)果為:
/xxx /yyy/zzz
hello
重定向
要實(shí)現(xiàn)網(wǎng)頁的重定向,需要導(dǎo)入flask下的redirect,然后在需要重定向的地方return redirect('url')即可,舉例:
from flask import Flask, redirect
app = Flask(__name__)
@app.route('/xxx')
def xxx():
return "你跳轉(zhuǎn)到了xxx"
@app.route('/redirect_url')
def main():
return redirect("/xxx")
#需要用到return redirect重定向
return "aaa"
if __name__ == '__main__':
app.run(debug=True)
訪問http://127.0.0.1:5000/redirect_url可以發(fā)現(xiàn)跳轉(zhuǎn)到了http://127.0.0.1:5000/xxx
請(qǐng)求上下文
每當(dāng)對(duì)該網(wǎng)頁進(jìn)行訪問時(shí),必然會(huì)發(fā)送請(qǐng)求,在flask中如果要讀取這些請(qǐng)求,那么就可以通過導(dǎo)入其下的request來讀取,舉例:
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/<id>')
def index1(id):
res = request
user = res.headers.get('user-agent')
return user
if __name__ == '__main__':
app.run(debug=True, host='180.212.38.57', port=6666)
分別用網(wǎng)站和python爬蟲訪問得到結(jié)果如下:
網(wǎng)站:
Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
python爬蟲:
python-requests/2.18.4
GET和POST
flask的路由默認(rèn)都是只允許get方法,如果想要使用post方法,就要在路由里給methods參數(shù)進(jìn)行配置,如果要判斷是哪種請(qǐng)求,可以通過request下的method屬性獲取,如果要獲取post參數(shù),則通過request.form這個(gè)字典來獲取,舉例:
from flask import Flask
from flask import request
app = Flask(__name__)
#兩種都能訪問
@app.route('/aaa', methods=['GET', 'POST'])
def index1():
if request.method == 'GET':
return "這是GET請(qǐng)求"
else:
return "這是POST請(qǐng)求<br>" + str(request.form)
#只能post
@app.route('/111', methods=['POST'])
def index2():
if request.method == 'GET':
return "GET是不可能GET了"
else:
return "這是POST請(qǐng)求"
#只能get
@app.route('/222')
def index3():
return "POST是不可能POST了,默認(rèn)只能GET"
if __name__ == '__main__':
app.run(debug=True)
分別通過get和post方法的爬蟲訪問:
import requests
res_aaa_get = requests.get("http://127.0.0.1:5000/aaa")
res_aaa_post = requests.post("http://127.0.0.1:5000/aaa", data={'user':'aaa','psd':'bbb'})
res_111_get = requests.get("http://127.0.0.1:5000/111")
res_111_post = requests.post("http://127.0.0.1:5000/111")
res_222_get = requests.get("http://127.0.0.1:5000/222")
res_222_post = requests.post("http://127.0.0.1:5000/222")
print(res_aaa_get.text)
print(res_aaa_post.text)
print(res_111_get.text)
print(res_111_post.text)
print(res_222_get.text)
print(res_222_post.text)
結(jié)果為:
這是GET請(qǐng)求
這是POST請(qǐng)求<br>ImmutableMultiDict([('user', 'aaa'), ('psd', 'bbb')])
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
這是POST請(qǐng)求
POST是不可能POST了,默認(rèn)只能GET
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
可以發(fā)現(xiàn)默認(rèn)只能get,可以根據(jù)需求設(shè)置可以用哪種方法或者都用
注:
當(dāng)獲取的是checkbox的數(shù)據(jù)時(shí),通過request.form.getlist()獲取
獲取請(qǐng)求參數(shù)
獲取請(qǐng)求頭是通過request下的headers獲取,如果要獲取傳遞的參數(shù),則可以通過args獲取,舉例:
from flask import Flask, redirect
from flask import request
app = Flask(__name__)
@app.route('/<id>')
def index1(id):
res = request.args
return str(res) + "<br>" + res['aa']
if __name__ == '__main__':
app.run(debug=True)
訪問http://127.0.0.1:5000/w?aa=sda&sda=gsda,可以看到輸出結(jié)果為:
ImmutableMultiDict([('aa', 'sda'), ('sda', 'gsda')])
sda
可以發(fā)現(xiàn)args下就是一個(gè)接收參數(shù)的字典,所以可以以字典方式對(duì)其進(jìn)行操作
獲取訪問ip
通過request下的remote_addr獲取,舉例:
@app.route('/')
def index():
return str(request.remote_addr)
#返回訪問的用戶ip地址
更多request下屬性參考:
https://www.cnblogs.com/wangjikun/p/6935592.html
https://blog.csdn.net/yannanxiu/article/details/53116652
return返回值
return返回參數(shù)有3個(gè),第一個(gè)是返回的內(nèi)容,也就是頁面展示的信息;第二個(gè)是響應(yīng)的狀態(tài)碼,默認(rèn)是200;第三個(gè)是一個(gè)字典,可以添加到http響應(yīng)當(dāng)中
響應(yīng)狀態(tài)碼
視圖函數(shù)默認(rèn)返回的狀態(tài)碼是200,如果要返回別的狀態(tài)碼,可以在return里第二個(gè)參數(shù)進(jìn)行設(shè)置,舉例:
@app.route('/xxx')
def xxx():
return "no found", 404
#返回404
通過爬蟲訪問觀察發(fā)現(xiàn):
import requests
a = requests.get('http://127.0.0.1:5000/xxx')
print(a.status_code)
結(jié)果為:
404
響應(yīng)狀態(tài)碼還可以通過flask下的abort實(shí)現(xiàn),但是這個(gè)和return返回狀態(tài)碼的控制不同,return返回狀態(tài)碼的情況下返回的頁面信息也是return里面的,就像前面的例子里返回的內(nèi)容是我們定義的no found,在abort下返回的內(nèi)容則是服務(wù)器自帶的錯(cuò)誤返回信息,舉例:
from flask import abort
@app.route('/yyy')
def yyy():
abort(404)
return "nothing"
通過爬蟲觀察發(fā)現(xiàn):
import requests
import pprint as p
a = requests.get('http://127.0.0.1:5000/yyy')
print(a.status_code)
print(a.text)
結(jié)果:
404
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
可以看出雖然返回的也是404,但是返回的內(nèi)容卻不是我們定義的"nothing",而是服務(wù)器自帶的異常語句
http響應(yīng)頭
視圖函數(shù)的第三個(gè)返回參數(shù),舉例:
@app.route('/xxx')
def xxx():
return "no found", 200, {'aaa':'sfas'}
#添加了'aaa'到響應(yīng)頭
通過爬蟲觀察發(fā)現(xiàn):
import requests
import pprint as p
a = requests.get('http://127.0.0.1:5000/xxx')
p.pprint(eval(str(a.headers)))
結(jié)果為:
{
'Content-Length': '8',
'Content-Type': 'text/html; charset=utf-8',
'Date': 'Wed, 03 Oct 2018 08:51:56 GMT',
'Server': 'Werkzeug/0.14.1 Python/3.5.2',
'aaa': 'sfas'
}
可以看到響應(yīng)的請(qǐng)求頭最后一行里多了個(gè)'aaa':'sfas'
response響應(yīng)
前面return返回的三個(gè)參數(shù)的方式還可以用flask下的make_response()來實(shí)現(xiàn),其接收的三個(gè)參數(shù)和return返回的三個(gè)是一樣的,在這里可以設(shè)置headers、cookie等,舉例:
@app.route('/xxx')
def xxx():
response = make_response('it will set a cookie')
#相當(dāng)于return的第一個(gè)參數(shù),即返回內(nèi)容
response.set_cookie('a_cookie', 'the content of cookie')
#設(shè)置cookie名和值
return response
#返回response
通過爬蟲觀察發(fā)現(xiàn):
import requests
import pprint as p
a = requests.get('http://127.0.0.1:5000/xxx')
p.pprint(eval(str(a.headers)))
print(a.text)
結(jié)果:
{
'Content-Length': '20',
'Content-Type': 'text/html; charset=utf-8',
'Date': 'Wed, 03 Oct 2018 09:33:41 GMT',
'Server': 'Werkzeug/0.14.1 Python/3.5.2',
'Set-Cookie': 'a_cookie="the content of cookie"; Path=/'
}
#response headers
it will set a cookie
#content
可以看到返回的響應(yīng)頭里set-cookie里多了鍵值
設(shè)置headers
如ajax跨域請(qǐng)求通過服務(wù)端方式的解決,可以參考:
https://blog.csdn.net/lovebyz/article/details/52584551
模板語言JInjia2
flask也有自己的模板語言,其可以實(shí)現(xiàn)主程序和靜態(tài)文件的交互等
調(diào)用靜態(tài)文件
使用步驟:
1.在主程序?qū)雽?duì)應(yīng)函數(shù):from flask import render_template
2.在當(dāng)前路徑下創(chuàng)建一個(gè)名為:templates的文件夾(flask調(diào)用靜態(tài)文件時(shí)會(huì)自動(dòng)在當(dāng)前路徑的templates文件夾下尋找)
3.如果需要往靜態(tài)文件里傳參,那么就在render_template里加入?yún)?shù)的鍵值
4.傳入的參數(shù)在靜態(tài)文件里通過語法:{{參數(shù)}}獲取
舉例:
###### 主程序 ######
from flask import Flask
from flask import render_template
app = Flask(__name__)
@app.route('/111')
def index1():
return render_template('a.html')
#會(huì)自動(dòng)尋找./templates下的a.html文件
@app.route('/222/<name>&<psd>')
def index2(name, psd):
return render_template('a.html', username=name, password=psd)
#傳入name和password
@app.route('/333')
def index3():
di = {
'username': 'aaa',
'password': 'bbb'
}
return render_template('a.html', **di)
#字典傳參,此時(shí)是傳字典的所有鍵值對(duì),而不是傳字典,所以在模板語言里di是不存在的
#如果想要傳整個(gè)字典,那么就可以di=di
if __name__ == '__main__':
app.run(debug=True)
###### 靜態(tài)文件a.html ######
<!DOCTYPE html>
<html>
<head>
<title>aaa</title>
</head>
<body>
<h1>hello {{username}}</h1>
<h2>you password is {{password}}</h2>
<!-- 顯示賬號(hào)密碼 -->
</body>
</html>
結(jié)果用爬蟲訪問可以看到結(jié)果:
import requests
res_111 = requests.get("http://127.0.0.1:5000/111")
res_222 = requests.get("http://127.0.0.1:5000/222/abc&def")
res_333 = requests.get("http://127.0.0.1:5000/333")
print(res_111.text)
print(res_222.text)
print(res_333.text)
部分結(jié)果為:
<h1>hello </h1>
<h2>you password is </h2>
#res_111
<h1>hello abc</h1>
<h2>you password is def</h2>
#res_222
<h1>hello aaa</h1>
<h2>you password is bbb</h2>
#res_333
注:
因?yàn)橛?code>render_template調(diào)用靜態(tài)頁面時(shí)會(huì)通過模板語言渲染該頁面,所以靜態(tài)文件里如果有像js之類的語句很容易被用模板語言進(jìn)行解析,結(jié)果報(bào)錯(cuò),所以一般被調(diào)用的文件建議只是單純的html,而沒有css和js的存在,如果文件有css和js,并且不想被模板語言渲染的話,可以用flask下的send_file()來不渲染直接調(diào)用文件,或者用url_for('static', filename='')直接靜態(tài)調(diào)用static路徑下的文件也可以(后面會(huì)說,但是前者是在py文件里用,后者是在支持模板語言的文件里用),這里對(duì)send_file()進(jìn)行舉例:
from flask import send_file
@app.route('/111')
def index1():
return send_file('templates/render.html')
#返回的頁面是不經(jīng)過模板語言渲染的
靜態(tài)文件獲取參數(shù)
前面演示了在靜態(tài)文件中用:{{屬性}}來獲取傳遞的參數(shù),其還有別的調(diào)用方法,比如傳來的如果是類,那么就可以用.來調(diào)用里面的屬性,舉例:
###### flask主要代碼 ######
class People():
name = 'dawson'
age = 20
def abc(self):
return "這是調(diào)用函數(shù)返回的"
#新建了一個(gè)類
@app.route('/333')
def index3():
p = People()
di = {
'people': p
}
return render_template('a.html', **di)
#傳入了一個(gè)類
###### a.html ######
<h1>hello {{people.name}}</h1>
<h2>you age is {{people['age']}}</h2>
{{people.abc()}}
<!-- 調(diào)用類的屬性和方法,用了.和[]兩種方式 -->
通過爬蟲訪問發(fā)現(xiàn)結(jié)果為:
<h1>hello dawson</h1>
<h2>you age is 20</h2>
這是調(diào)用函數(shù)返回的
可以看出.和[]兩種方式都可以獲取參數(shù)
if判斷
模板語言可以在靜態(tài)文件里進(jìn)行if判斷,基本語法如下:
{% if 條件 %}
...
{% elif %}
...
{% else %}
...
{% endif %}
使用舉例
###### 主程序主要代碼 ######
@app.route('/111')
def index1():
return render_template('a.html')
@app.route('/222/<name>&<psd>')
def index2(name, psd):
return render_template('a.html', username=name, password=psd)
#可以看出111的時(shí)候沒傳參,222的時(shí)候傳參了
###### a.html ######
{% if username %}
<h1>{{username}},你好</h1>
{% else %}
<h1>請(qǐng)登錄</h1>
{% endif %}
用爬蟲分別訪問,結(jié)果為:
<h1>請(qǐng)登錄</h1>
#http://127.0.0.1:5000/111
...
<h1>abc,你好</h1>
#http://127.0.0.1:5000/222/abc&def
for循環(huán)
基本語法:
{% for xxx in xxx %}
...
{% endfor %}
使用舉例
###### 主程序 ######
@app.route('/333')
def index3():
di = {
'name':'abc',
'age':20
}
return render_template('a.html', di=di)
#傳入一個(gè)字典
###### a.html ######
{% for k,v in di.items() %}
{{k}}:{{v}}<br>
<!-- 循環(huán)輸出鍵值 -->
{% endfor %}
{% for i in range(2) %}
{{i}}<br>
<!-- 也可以用range這樣python自帶的函數(shù) -->
{% endfor %}
結(jié)果為:
name:abc<br>
age:20<br>
0<br>
1<br>
過濾器
模板語言里有很多過濾器,其能夠?qū)鱽淼臄?shù)據(jù)進(jìn)行一定處理后展示出來,比如默認(rèn)過濾器(default)可以當(dāng)數(shù)據(jù)不存在時(shí)設(shè)置默認(rèn)值,長(zhǎng)度過濾器(length)可以計(jì)算參數(shù)長(zhǎng)度等,基本語法如下:
{{參數(shù) | 過濾器 }}
當(dāng)然不同過濾器語法也有些不同,舉例:
###### 主程序 ######
@app.route('/222/<name>&<psd>')
def index2(name, psd):
return render_template('a.html', username=name, password=psd)
@app.route('/333')
def index3():
di = {
'name':'abc',
'age':20,
'li':[1,2,3,4,5]
}
return render_template('a.html', **di)
###### a.html ######
{{ username | default('默認(rèn)用戶')}}
<!-- 默認(rèn)過濾器,設(shè)置默認(rèn)值,如果username不存在時(shí)為默認(rèn)用戶 -->
<br>
{{ li | length}}
<!-- 長(zhǎng)度過濾器,計(jì)算列表、字符串等數(shù)據(jù)長(zhǎng)度,li的長(zhǎng)度,li不存在默認(rèn)為0 -->
分別訪問結(jié)果為:
默認(rèn)用戶
5
#http://127.0.0.1:5000/333
abc
0
#http://127.0.0.1:5000/222/abc&def
更多過濾器參考:http://www.itdecent.cn/p/3127ac233518
自定義過濾器參考:https://blog.csdn.net/fanlei5458/article/details/80341278
繼承和block
像新聞之類的網(wǎng)站的新聞頁基本頁面結(jié)構(gòu)都相同,如果說每個(gè)頁面都去重復(fù)寫這個(gè)結(jié)構(gòu)語句顯然太浪費(fèi),因此就可以寫一個(gè)專門的模板文件給這些新聞頁繼承,然后在里面的某個(gè)版塊寫各自的新聞即可,這里要實(shí)現(xiàn)這種情景需要執(zhí)行以下幾步:
1.寫一個(gè)模板文件xxx.html
2.在模板文件可以被添加內(nèi)容的版塊寫上{% block 版塊名 %}{% endblock %}
3.在要繼承該模板的文件里寫上{% extends '模板名' %},然后通過語句:{% block 版塊名 %}添加在版塊的內(nèi)容{% endblock %},來往版塊里添加自己的內(nèi)容
4.模板文件的版塊里可以寫內(nèi)容,文件繼承后對(duì)應(yīng)版塊不會(huì)顯示該內(nèi)容,如果想要該內(nèi)容可以通過{{super()}}來獲取
舉例:
###### 模板文件b.html ######
<!DOCTYPE html>
<html>
<head>
<title>模板文件</title>
</head>
<body>
<h1>這是父模板的內(nèi)容</h1>
<hr>
{% block main %}
<h1>這是父模板塊1的內(nèi)容</h1>
{% endblock %}
{% block main2 %}
<h1>這是父模板塊2的內(nèi)容</h1>
{% endblock %}
</body>
</html>
###### 繼承模板的文件 ######
{% extends 'b.html' %}
{% block main %}
{{super()}}
{% endblock %}
{% block main2 %}
<hr>
<h1>這里是子模板的內(nèi)容</h1>
{% endblock %}
可以看出模板文件新建了兩個(gè)版塊:main和main2,然后其他文件繼承他,并在這些版塊里寫內(nèi)容,其中版塊1通過super()繼承了模板文件的內(nèi)容,版塊2沒有繼承內(nèi)容,最終頁面結(jié)果為:
...
<title>模板文件</title>
...
<h1>這是父模板的內(nèi)容</h1>
<hr>
<h1>這是父模板塊1的內(nèi)容</h1>
<hr>
<h1>這里是子模板的內(nèi)容2</h1>
調(diào)用本地文件
前面的方法調(diào)用文件的目錄都是在服務(wù)器上,比如在html里想要導(dǎo)入本地的e.js可能會(huì)寫:
<script type="text/javascript" src="e.js"></script>
結(jié)果卻是顯示文件找不到,因?yàn)榇藭r(shí)他尋找的路徑是:http://127.0.0.1:5000/e.js,而不是本地的相對(duì)路徑,所以為了導(dǎo)入這些本地路徑的文件,需要下面幾步:
1.創(chuàng)建一個(gè)static文件夾,里面用來存放各種靜態(tài)文件
2.通過:{{url_for('static', filename='文件名')}},來調(diào)用static路徑下的文件
舉例:
<script type="text/javascript" src="{{url_for('static', filename='e.js')}}"></script>
#調(diào)用了static/e.js
<a href="{{url_for('static', filename='a.html')}}">aaa</a>
#跳轉(zhuǎn)到static/a.html文件,并且不會(huì)用模板語言解析
注:
這種調(diào)用本地文件的方式是靜態(tài)調(diào)用,因此不會(huì)用模板語言對(duì)其進(jìn)行解析,而且是在支持模板語言的地方使用,在主程序中用這個(gè)返回的是個(gè)路徑,不會(huì)調(diào)用文件,要調(diào)用靜態(tài)文件就用send_file()
flash消息機(jī)制
flash是基于session的一種消息處理機(jī)制,其可以傳遞自定義的flash信息,而后臺(tái)或者前臺(tái)通過get_flashed_messages()方法獲取到flash信息,并且該消息機(jī)制有個(gè)特點(diǎn)就是只能在一次請(qǐng)求中使用,之后消失。使用舉例:
- 后端:
from flask import Flask, flash, get_flashed_messages, render_template
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
# 使用flash時(shí)需要配置secret_key,從而保證flash和session的唯一性,以免被別的session獲取到信息
@app.route('/')
def test():
flash("msg")
# 往前臺(tái)傳遞消息
return render_template("test.html")
if __name__ == '__main__':
app.run(debug=True, port=5000)
- 前臺(tái):
<body>
{{ get_flashed_messages() }}
{#使用內(nèi)置方法獲取flash,結(jié)果為:['msg'],可以看到獲取的是flash傳遞的消息列表#}
</body>
基于flash的重定向消息傳遞
該消息機(jī)制還可以解決重定向時(shí)的頁面信息交互,比如訪問路由A時(shí),在路由A中重定向跳轉(zhuǎn)到了路由B,那么A中的信息就容易丟失而無法傳遞到B,一種解決方式是重定向時(shí)往地址里添加get參數(shù),但這樣會(huì)導(dǎo)致一個(gè)問題:重定向后的頁面如果刷新則每次都會(huì)獲取一樣的get參數(shù),并且也可能被惡意利用,在前臺(tái)傳入一些敏感數(shù)據(jù)。為了避免這些情況,可以使用flash來傳遞消息,因?yàn)閒lash基于session,所以即使重定向以后,消息還是能夠傳遞的,舉例:
from flask import Flask, flash, get_flashed_messages, render_template, redirect
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
# 使用flash時(shí)需要配置secret_key,從而保證flash和session的唯一性,以免被別的session獲取到信息
@app.route('/')
def test():
flash("msg0", 'aaa')
# 第一個(gè)代表返回信息,第二個(gè)代表flash類型,可以自定義,然后前臺(tái)過濾篩選對(duì)應(yīng)類型的信息
flash("msg1", "info")
flash("msg2", "erros")
return redirect('/aaa', 302)
# 重定向到另一個(gè)路由,由于flash是基于session,所以可以傳遞過去
@app.route('/aaa')
def aaa():
msg = get_flashed_messages(category_filter=['aaa', 'info'])
# 篩選出類型為aaa或info的flash信息
if not msg:
return "nothing"
return str(msg)
# 結(jié)果會(huì)收到:['msg0', 'msg1']
if __name__ == '__main__':
app.run(debug=True, port=5000)
數(shù)據(jù)庫操作
flask的靈活性幾乎不限制你使用哪種數(shù)據(jù)庫,所以一種方法是你可以直接導(dǎo)入那個(gè)數(shù)據(jù)庫模塊,然后進(jìn)行操作,另一種就是用flask-sqlalchemy框架(pip install flask-sqlalchemy)來管理數(shù)據(jù)庫,使用該模塊能夠通過orm模型對(duì)數(shù)據(jù)庫進(jìn)行操作,而不需要像原來那樣通過sql語句來操作,從而使得操作更加簡(jiǎn)便。這里也主要講flask-sqlalchemy結(jié)合mysql的使用
flask-sqlalchemy連接池問題
flask-sqlalchemy是基于sqlalchemy的數(shù)據(jù)庫orm框架,兩個(gè)使用方法大同小異,后面幾節(jié)介紹的是flask-sqlalchemy的操作方法(但因?yàn)檫B接池的原因,所以本節(jié)先介紹一點(diǎn)sqlalchemy的針對(duì)連接池的解決方案)
在flask-sqlalchemy中,進(jìn)行數(shù)據(jù)庫session操作后,使用close()方法并不會(huì)將session關(guān)閉,而是存在一個(gè)連接池里進(jìn)行復(fù)用,但是連接池滿了會(huì)容易讓程序崩了(誰也不希望網(wǎng)站跑著跑著崩了吧...),一種處理方法是設(shè)置連接池最大數(shù)量:SQLALCHEMY_POOL_SIZE=數(shù)量(默認(rèn)是5),但是這個(gè)如果調(diào)用數(shù)據(jù)庫少還好,一旦到了上限終究也不是辦法,此時(shí)建議另一種方法:配置時(shí)選擇不使用連接池,下面給上配置代碼:
from sqlalchemy import create_engine // 初始化配置方法
from sqlalchemy.orm import sessionmaker // 創(chuàng)建orm對(duì)象
from sqlalchemy.pool import NullPool // 不使用連接池
SQLALCHEMY_DATABASE_URI='mysql+mysqlconnector://root:password@localhost:3306/test'
# 初始化配置,格式:數(shù)據(jù)庫類型+數(shù)據(jù)庫驅(qū)動(dòng)名://用戶名:密碼@IP:端口/數(shù)據(jù)庫名
engine = create_engine(SQLALCHEMY_DATABASE_URI, poolclass=NullPool)
# 初始化配置,并選擇不使用連接池
DB = sessionmaker(bind=engine)
# 創(chuàng)建一個(gè)orm操作對(duì)象
那么接下來使用的時(shí)候只需要實(shí)例化一個(gè)orm的session對(duì)象,即可進(jìn)行操作了,并且要記得在操作完成時(shí)使用close()進(jìn)行關(guān)閉,具體可參考廖雪峰老師的教程:https://www.liaoxuefeng.com/wiki/1016959663602400/1017803857459008
連接池問題可參考:
https://blog.csdn.net/Yaokai_AssultMaster/article/details/80958052
https://blog.csdn.net/weiwangchao_/article/details/80185009
連接數(shù)據(jù)庫
連接數(shù)據(jù)庫需要執(zhí)行以下幾步:
1.from flask_sqlalchemy import SQLAlchemy
2.實(shí)例化SQLAlchemy
3.配置數(shù)據(jù)庫參數(shù),最好寫在配置文件
3.導(dǎo)入配置后,通過create_all()測(cè)試是否配置成功
舉例
###### 配置文件config.py ######
# 連接mysql格式:dialect+driver://username:password@host:port/database
DIALECT = 'mysql' #數(shù)據(jù)庫
DRIVER = 'mysqldb' #操作的驅(qū)動(dòng)
#驅(qū)動(dòng)去https://www.lfd.uci.edu/~gohlke/pythonlibs/下安裝對(duì)應(yīng)版本mysqlclient,2.x版本安裝mysql-python
USERNAME = 'root'
PASSWORD = ''
HOST = '127.0.0.1'
PORT = '3306'
DATABASE = ''
SQLALCHEMY_DATABASE_URI = "%s+%s://%s:%s@%s:%s/%s?charset=utf8" % (DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE)
SQLALCHEMY_TRACK_MODIFICATIONS = False
###### 主程序 ######
from flask_sqlalchemy import SQLAlchemy
import config
app = Flask(__name__)
app.config.from_object(config)
#導(dǎo)入配置
db = SQLAlchemy(app)
#實(shí)例化
db.create_all()
#一般是創(chuàng)建表或者數(shù)據(jù)庫用,這里用來測(cè)試是否連接成功,連接失敗程序是跑不起來的
注:
不同數(shù)據(jù)連接的配置不同,可以參考:https://www.cnblogs.com/fengff/p/8669363.html
創(chuàng)建數(shù)據(jù)表
在orm模型當(dāng)中,一個(gè)表就是一個(gè)類,不過這個(gè)類要繼承于db.Model,一個(gè)列通過db.Column來創(chuàng)建,創(chuàng)建時(shí)需要定義數(shù)據(jù)類型和字段修飾等,舉例:
from flask_sqlalchemy import SQLAlchemy
import config
app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)
class clothes(db.Model):
#繼承自db.Model
__tablename__ = 'clothes_shop'
#設(shè)置表名,不設(shè)置默認(rèn)為類名
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
#整型,主鍵,自增
name = db.Column(db.String(50), nullable=False)
#char和varchar都是用String,非空
cost = db.Column(db.Float, nullable=True)
content = db.Column(db.Text, nullable=True)
db.create_all()
#創(chuàng)建表和數(shù)據(jù)庫用的
注:
上面創(chuàng)建表的語句,如果不存在該表則會(huì)創(chuàng)建一個(gè),如果存在的話,就不會(huì)對(duì)原來的表進(jìn)行修改,而定義的幾個(gè)列的意思是會(huì)用到表里的哪幾列數(shù)據(jù)。此時(shí)如果定義了表里沒有的列則用到該表時(shí)會(huì)報(bào)錯(cuò)
注2:
一些常見的數(shù)據(jù)類型:
db.Integer
db.Float
db.String #這個(gè)包括char和varchar
db.Text
db.Boolean
db.Date
db.DateTime
db.Time
添加數(shù)據(jù)
步驟:
1.實(shí)例化該類(表),并把要添加的數(shù)據(jù)作為參數(shù)傳入
2.通過db.session.add()添加
3.通過db.session.commit()提交事務(wù)
舉例:
@app.route('/111')
def index1():
clothes = Clothes(name='bbb', cost=23, content='sdaf')
#添加數(shù)據(jù)的內(nèi)容
db.session.add(clothes)
#添加數(shù)據(jù)
db.session.commit()
#操作都是事務(wù)機(jī)制,所以只有提交后才會(huì)真正改變數(shù)據(jù)
return "True"
注:
對(duì)于大量數(shù)據(jù)的插入,如果使用add循環(huán)插入,效率將十分低下,此時(shí)可以使用bulk_save_objects([orm對(duì)象1, orm對(duì)象2, ...])或者bulk_insert_mappings(orm對(duì)象類, [對(duì)象參數(shù)字典1, 對(duì)象參數(shù)字典2, ...])進(jìn)行批量插入,舉例:
# bulk_save_objects示例
li = [Clothes(name='aaa', cost=10, content='aaa'), Clothes(name='bbb', cost=23, content='sdaf')]
db.session.bulk_save_objects(li)
# bulk_insert_mappings示例
li = [{"name": "aaa", "cost": 10, "content":"aaa"}, {"name": "bbb", "cost": 20, "content":"bbb"}]
db.session.bulk_insert_mappings(Clothes, li)
批量插入操作對(duì)比:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config
from time import time
app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = 'user'
name = db.Column(db.String(45), nullable=False, primary_key=True)
password = db.Column(db.String(45))
money = db.Column(db.String(45))
db.create_all()
@app.route('/')
def index():
start = time()
# 方式一:基于orm對(duì)象一條一條插入,耗時(shí):12s(相當(dāng)于執(zhí)行100000條語句,且每次都需要實(shí)例化orm對(duì)象,效率低且開銷大,不建議)
for i in range(100000):
user = User(name=str(i), password=str(i), money=str(i))
db.session.add(user)
# 方式二:基于bulk_save_objects插入批量orm對(duì)象,耗時(shí):6s(需要實(shí)例化大量的orm對(duì)象,開銷也不?。? # li = []
# for i in range(100000):
# user = User(name=str(i), password=str(i), money=str(i))
# li.append(user)
# db.session.bulk_save_objects(li)
# 方式三:基于bulk_insert_mappings插入,耗時(shí):4s(直接傳入需要映射的對(duì)象和參數(shù)字典,相對(duì)消耗小一些,在效率上和安全性能上較為平衡)
# li = []
# for i in range(100000):
# li.append({"name": str(i), "password": str(i), "money":str(i)})
# db.session.bulk_insert_mappings(User, li)
# 方式四:合并成單條語句插入,耗時(shí):2.6s(合成單條sql語句,并直接執(zhí)行,效率較高,但沒有對(duì)sql進(jìn)行檢查校驗(yàn),安全性能差一些)
# s = "insert into user values"
# for i in range(100000):
# s += "({}, {}, {}),".format(i, i, i)
# db.session.execute(s[:-1])
db.session.commit()
cost_time = time() - start
return str(cost_time)
if __name__ == '__main__':
app.run(debug=True)
查詢數(shù)據(jù)
一般格式:表名.query.過濾器.過濾函數(shù)
常用的過濾器有:all()-查詢所有、filter()-過濾查詢、limit()-限制結(jié)果數(shù)量、order_by()-按條件排序、group_by()-按條件分組
常用的過濾函數(shù)有:all()-以列表返回所有數(shù)據(jù)、first()-返回第一個(gè),沒有就None、first_or_404()-返回第一個(gè),沒有就返回404響應(yīng)、get()-查找主鍵對(duì)應(yīng)行、get_or_404()-...沒有就404、count()-返回查詢結(jié)果數(shù)量
舉例:
from sqlalchemy import or_
@app.route('/111')
def index1():
clothes = Clothes.query.all()
#查詢表里所有數(shù)據(jù)
clothes1 = Clothes.query.filter(Clothes.name == 'bbb').all()
#查詢表里name='bbb'的所有數(shù)據(jù),all()可以查所有的,返回的是個(gè)列表
clothes2 = Clothes.query.filter(Clothes.name == 'bbb').first()
#first相當(dāng)于返回all的第一條
clothes3 = Clothes.query.filter(or_(Clothes.name == 'bbb', Clothes.name == 'aaa')).first()
#查詢name為aaa或bbb的第一條數(shù)據(jù)
return str(clothes1[0].name) + str(clothes2.name)
#輸出查詢結(jié)果的name
更新數(shù)據(jù)
步驟:
1.查詢要更新的數(shù)據(jù)
2.從查詢結(jié)果中更新數(shù)據(jù)
3.提交事務(wù)
舉例:
@app.route('/111')
def index1():
clothes = Clothes.query.filter(Clothes.name == 'bbb').first()
clothes.name = 'aaa'
#把第一條name為bbb的改為aaa
db.session.commit()
return str(clothes.name)
#可以發(fā)現(xiàn)返回的數(shù)據(jù)是aaa,數(shù)據(jù)庫對(duì)應(yīng)數(shù)據(jù)也修改了
注:
批量更新示例:
clothes = Clothes.query.filter(Clothes.name == 'aaa').update({Clothes.name: 'bbb'})
# 所有name為aaa的數(shù)據(jù)的name都更新為bbb
刪除數(shù)據(jù)
步驟:
1.查詢要?jiǎng)h除的數(shù)據(jù)
2.將查詢的結(jié)果數(shù)據(jù)刪除
3.提交事務(wù)
舉例:
@app.route('/111')
def index1():
clothes = Clothes.query.filter(Clothes.name == 'aaa').first()
db.session.delete(clothes)
#刪除數(shù)據(jù)
db.session.commit()
return "True"
注:
批量刪除示例:
clothes = Clothes.query.filter(Clothes.name == 'aaa').delete()
索引
- 主鍵索引:直接在字段定義時(shí)設(shè)置
primary_key屬性值為True,即可,舉例:
xxx = db.Column(db.String(32), nullable=False, primary_key=True)
- 普通索引:直接在字段定義時(shí)設(shè)置
index屬性值為True,即可,舉例:
xxx = db.Column(db.String(32), nullable=False, index=True)
- 聯(lián)合索引:在類的
__table_args__屬性中使用Index創(chuàng)建,第一個(gè)參數(shù)為索引名,后面?zhèn)魅攵鄠€(gè)字段,舉例:
__table_args__ = (
db.Index('聯(lián)合索引名字', '字段1', '字段2', '...'),
)
- 唯一索引:直接在字段定義時(shí)設(shè)置
unique屬性值為True,即可,舉例:
xxx = db.Column(db.String(32), nullable=False, unique=True)
- 聯(lián)合唯一索引:在類的
__table_args__屬性中使用UniqueConstraint創(chuàng)建,傳入多個(gè)字段,并設(shè)置name參數(shù)值為聯(lián)合索引名,舉例:
__table_args__ = (
db.UniqueConstraint('字段1', '字段2', '...', name='聯(lián)合索引名字'),
)
外鍵
舉例:
class Clothes(db.Model):
__tablename__ = 'clothes_shop'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50))
cost = db.Column(db.Float, nullable=True)
psd = db.Column(db.String(20), nullable=False)
class Food(db.Model):
__tablename__ = 'food'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String, db.ForeignKey('clothes_shop.name'),nullable=False)
#這是name為clothes_shop表下name的外鍵
#注意外鍵設(shè)置要在字符修飾前(關(guān)鍵字參數(shù)必須在位置參數(shù)后)
cost = db.Column(db.Integer, nullable=True)
number = db.Column(db.Integer)
更多orm操作參考:
https://www.cnblogs.com/zhaoyunlong/p/10368654.html
Session操作
session一般都是存在服務(wù)端,這也是其和cookie的主要區(qū)別。但是在flask中有些不一樣,session都是加密后然后發(fā)送到cookie處
添加Session
步驟
1.from flask import session
2.配置SECRET_KEY的值
3.設(shè)置session里的鍵值對(duì)(session相當(dāng)于一個(gè)字典)
舉例:
from flask import session
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
#生成24位隨機(jī)字符串
@app.route('/111')
def index1():
session['username'] = 'dawson'
#設(shè)置session里的值,其相當(dāng)于一個(gè)字典
return "True"
訪問后會(huì)發(fā)現(xiàn)瀏覽器里多了個(gè)127.0.0.1的cookie,其名為session
讀取session
因?yàn)閟ession是類似于一個(gè)字典的存在,所以讀取就像字典那樣讀取即可,舉例:
@app.route('/222')
def index2():
return session.get('username', 'nothing')
#返回session['username']的值,用get設(shè)置缺省值,防止不存在報(bào)錯(cuò)
刪除session
還是像字典那樣操作,舉例:
@app.route('/333')
def index3():
session.clear()
#清空session
return "Clear"
@app.route('/444')
def index4():
session.pop('username')
#彈出session中的username
return "pop"
設(shè)置session有效期
設(shè)置session.permanent為True即可,默認(rèn)是31天,可以通過配置PERMANENT_SESSION_LIFETIME來修改有效時(shí)間,舉例:
from flask import session
from datetime import timedelta
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)
#設(shè)置1小時(shí)有效時(shí)間,格式為時(shí)間格式,所以需要用到datetime下的timedelta
#沒設(shè)置的話默認(rèn)31天
@app.route('/111')
def index1():
session['username'] = 'dawson'
session['password'] = '123456'
session.permanent = True
#設(shè)置其有有效時(shí)間,不設(shè)置默認(rèn)會(huì)話關(guān)閉即失效
return "True"
全局變量g
一般函數(shù)里的變量都只有自己能用,因此會(huì)通過global設(shè)置為全局變量,但是當(dāng)調(diào)用別的文件里的函數(shù)時(shí),global聲明也沒用了,只能通過函數(shù)傳參,但是在flask里,一般調(diào)用的函數(shù)都是別的文件里的,因此flask下有一個(gè)g模塊可以實(shí)現(xiàn)多個(gè)文件里的全局變量,使用步驟:
1.from flask import g
2.通過g.變量名設(shè)置全局變量
3.通過g.變量名來調(diào)用
舉例:
###### 主程序 ######
from flask import Flask
from flask import g
import config
@app.route('/222')
def index2():
g.x = 1000
#定義全局變量x
config.abc()
#調(diào)用abc()函數(shù),沒有傳參
return str(g.x)
#會(huì)發(fā)現(xiàn)x變成1001
###### 被調(diào)用文件config.py ######
from flask import g
#被調(diào)用文件也要導(dǎo)入g
def abc():
g.x += 1
#全局變量x+1
可以看出g.x在兩個(gè)文件里共用了。但是要注意的是這種全局變量只在一次請(qǐng)求中有效,下次請(qǐng)求又沒了,比如下面這個(gè):
@app.route('/222')
def index2():
g.x = 1000
config.abc()
g.x *= 2
return str(g.x)
@app.route('/333')
def index3():
return str(g.x)
先訪問/222再訪問/333,結(jié)果會(huì)發(fā)現(xiàn)g.x不存在,因?yàn)榈诙卧L問時(shí),/333里沒有定義g.x,所以自然就沒了,因此g.x一般用于傳遞用戶數(shù)據(jù),或者是在一次請(qǐng)求的地方使用
鉤子函數(shù)
一般函數(shù)都是按順序執(zhí)行,比如執(zhí)行函數(shù)A->函數(shù)B,鉤子函數(shù)則可以插入到他們之間運(yùn)行,結(jié)果就成了函數(shù)A->鉤子函數(shù)->函數(shù)B,有種類似類里的魔法方法的感覺。
在編寫上類似于一個(gè)裝飾器,只需定義其執(zhí)行的函數(shù)即可
before_request
在請(qǐng)求前執(zhí)行,舉例:
@app.route('/333')
def index3():
return "True"
@app.route('/444')
def index4():
return "True"
#鉤子函數(shù)
@app.before_request
def abc():
print('aaa')
結(jié)果會(huì)發(fā)現(xiàn)每次不論訪問哪個(gè)鏈接時(shí)都會(huì)先在控制臺(tái)輸出'aaa'
after_request
返回請(qǐng)求時(shí)執(zhí)行,會(huì)接收response對(duì)象,舉例:
@app.after_request
def deal_response(response):
print("返回?cái)?shù)據(jù)!")
return response
context_processor
上下文處理器鉤子函數(shù),需要返回一個(gè)字典,這個(gè)字典會(huì)傳給所有模板對(duì)象,舉例:
###### 主程序 ######
@app.route('/333')
def index3():
return render_template('a.html')
@app.route('/444')
def index4():
return render_template('b.html')
@app.context_processor
def abc():
return {'username':'dawson'}
#返回一個(gè)字典
###### a.html ######
你好:{{username}}
###### b.html ######
<h1>{{username}}</h1>
結(jié)果會(huì)發(fā)現(xiàn)訪問上面的兩個(gè)鏈接,跳轉(zhuǎn)時(shí)頁面里都獲得了字典內(nèi)容
常用鉤子函數(shù)參考
http://www.itdecent.cn/p/17fa68cf38a6
藍(lán)圖
用于定義某一路由功能下對(duì)應(yīng)的功能模塊,類似django中的app,使用藍(lán)圖能讓我們更加規(guī)范合理的進(jìn)行代碼開發(fā),使用步驟如下:
- 創(chuàng)建藍(lán)圖
- 編寫藍(lán)圖下視圖函數(shù)
- 注冊(cè)藍(lán)圖
簡(jiǎn)單示例
為了方便示例,這里將藍(lán)圖代碼都編寫在一個(gè)文件當(dāng)中(然而在實(shí)際的開發(fā)中,最好還是分別給不同的藍(lán)圖創(chuàng)建對(duì)應(yīng)的文件編寫視圖函數(shù)):
from flask import Flask, Blueprint
app = Flask(__name__)
shop = Blueprint('shop', __name__)
# 創(chuàng)建一個(gè)藍(lán)圖
# shop = Blueprint('shop', __name__, static_folder='/static', template_folder='/templates')
# 多了藍(lán)圖的靜態(tài)資源和模板資源路徑的配置
@shop.route('/')
# 藍(lán)圖下編寫路由函數(shù)
def shop_index():
return "shop page"
app.register_blueprint(shop, url_prefix="/shop")
# 注冊(cè)藍(lán)圖,并配置路由前綴
# 注意需要在編寫完藍(lán)圖代碼后注冊(cè)藍(lán)圖
@app.route('/')
def index():
return "main page"
if __name__ == '__main__':
app.run()
基于藍(lán)圖的模塊化開發(fā)
通過藍(lán)圖我們可以很方便的進(jìn)行模塊化開發(fā),并且很好的隔離開各部分代碼,還有如一般作用于全局的鉤子函數(shù)也可以只注冊(cè)才某個(gè)藍(lán)圖下生效,例如還是對(duì)上面的代碼進(jìn)行修改實(shí)現(xiàn)指定路由下才生效的鉤子函數(shù):
from flask import Flask, Blueprint
app = Flask(__name__)
shop = Blueprint('shop', __name__)
@shop.route('/')
def shop_index():
return "shop page"
@shop.after_request
# 僅藍(lán)圖下有效的鉤子函數(shù)
def deal_shop_response(response):
print("局部鉤子")
return response
app.register_blueprint(shop, url_prefix="/shop")
@app.route('/')
def index():
return "main page"
@app.after_request
# 全局下有效的鉤子函數(shù)
def deal_response(response):
print("全局鉤子!")
return response
if __name__ == '__main__':
app.run()
上面我們實(shí)現(xiàn)了兩個(gè)鉤子函數(shù),一個(gè)全局鉤子(deal_response)、一個(gè)局部鉤子(deal_shop_response),結(jié)果訪問/路由時(shí)只有全局鉤子執(zhí)行,訪問/shop/下路由時(shí)全局和局部鉤子都執(zhí)行(先局部、再全局)
其他
基于CBV開發(fā)
前面的示例都是基于FBV(基于函數(shù)式視圖)開發(fā),但flask中也提供了CBV(基于類視圖)開發(fā),簡(jiǎn)單示例如下:
from flask import Flask, views
app = Flask(__name__)
def auth(func):
def wrapper(*args, **kwargs):
print("開始認(rèn)證")
result = func(*args, **kwargs)
print("認(rèn)證成功")
return result
return wrapper
class Test(views.MethodView):
# 基于CBV開發(fā)需要繼承views.MethodView類
methods = ['GET', 'POST']
# 配置允許請(qǐng)求的方式
decorators = [auth, ]
# 配置裝飾器
def get(self):
return "get方法"
def post(self):
return "post方法"
app.add_url_rule("/test", view_func=Test.as_view(name='test'))
# 添加路由
if __name__ == '__main__':
app.run(debug=True, port=5000)
項(xiàng)目自動(dòng)reload導(dǎo)致崩潰問題
開發(fā)時(shí),由于使用flask自帶的app.run方法運(yùn)行,經(jīng)常容易因?yàn)樾薷拇a或者測(cè)試而導(dǎo)致程序崩潰,此時(shí)可以在app.run里配置下面的參數(shù)禁止程序reload,從而避免問題:
use_reloader=False
并發(fā)處理問題
項(xiàng)目部署時(shí)如果使用flask自帶的app.run方法默認(rèn)是只有單線程的,因此如果同時(shí)訪問時(shí)將可能出現(xiàn)阻塞問題(比如進(jìn)入要路由要等待10秒,那么兩個(gè)請(qǐng)求同時(shí)訪問時(shí),必須是等待前一個(gè)訪問結(jié)束后,后一個(gè)才能訪問),一種解決方法是可以在app.run方法里加上配置參數(shù):threaded=True,代表允許多線程,或者配置參數(shù)processes=True,代表允許多進(jìn)程(注意線程進(jìn)程只能二選一),代碼如下所示:
app.run(threaded=True)
# 支持多線程
# app.run(processes=True)
# 支持多進(jìn)程
但這種方法終究只能算作一種臨時(shí)方案,在面對(duì)大量的訪問請(qǐng)求場(chǎng)景,一般情況下還是通過在外層通過nginx增加服務(wù)器數(shù)量和負(fù)載均衡,內(nèi)層使用uwsgi進(jìn)行多進(jìn)程處理,或者gunicron、gevent等協(xié)程處理來實(shí)現(xiàn)高并發(fā)性能(內(nèi)層的這幾種處理方案基本都是需要在Linux環(huán)境下才能夠?qū)崿F(xiàn),不過有個(gè)mod_wsgi是基于apache的,貌似支持windows)
gevent處理并發(fā)示例
使用自帶的app.run()無法支持gevent的協(xié)程處理,所以需要使用gevent.pywsgi下的WSGIServer實(shí)現(xiàn),舉例:
from flask import Flask
from gevent.pywsgi import WSGIServer
from gevent import monkey
import time
monkey.patch_all()
app = Flask(__name__)
@app.route('/')
def test():
time.sleep(10)
return 'test'
@app.route('/2')
def test2():
time.sleep(10)
return 'test2'
if __name__ == '__main__':
http_server = WSGIServer(('127.0.0.1', 5000), app)
http_server.serve_forever()
跨域處理
流內(nèi)容傳輸
后臺(tái)可以使用stream_with_context包裝一個(gè)流內(nèi)容的生成器,并通過Response對(duì)象包裝返回,stream_with_context內(nèi)部能夠保持服務(wù)端和客戶端通信的上下文,從而不斷向客戶端發(fā)送數(shù)據(jù),直到生成器拋出異常。然后前臺(tái)通過一些異步標(biāo)簽,如<img>/<video>/...來接收數(shù)據(jù),但最好不要直接頁面請(qǐng)求或者通過ajax進(jìn)行獲取,因?yàn)榉?wù)端會(huì)一直等待全部數(shù)據(jù)發(fā)送完畢才會(huì)斷開,期間請(qǐng)求將會(huì)一直處于等待狀態(tài),從而造成等待超時(shí)之類的情況。
參考:
https://www.cnblogs.com/jsben/p/4909984.html
https://dormousehole.readthedocs.io/en/latest/patterns/streaming.html
流內(nèi)容傳輸另一種方式:可以通過websocket與服務(wù)器進(jìn)行連接,然后服務(wù)器主動(dòng)將數(shù)據(jù)內(nèi)容發(fā)送給客戶端,舉例:
后端
from flask import Flask
from flask_sockets import Sockets
import datetime
import time
import random
import base64
from glob import glob
app = Flask(__name__)
sockets = Sockets(app)
path = "xxx/*.jpg"
# 圖片文件路徑
li_path = glob(path)
li_img = []
for each in li_path:
with open(each, "rb") as f:
content = f.read()
li_img.append(content)
li = [base64.encodebytes(each) for each in li_img]
@sockets.route('/echo')
def echo_socket(ws):
while not ws.closed:
ws.send(random.choice(li)) #發(fā)送數(shù)據(jù)
time.sleep(.3)
if __name__ == "__main__":
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
server = pywsgi.WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
print('server start')
server.serve_forever()
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.2.0/jquery.js"></script>
</head>
<body>
<img src="" />
<script>
var ws = new WebSocket("ws://127.0.0.1:5000/echo");
ws.onmessage = function (event) {
var reader = new FileReader();
reader.onload = function (event) {
$("img").attr("src", `data:image/jpg;base64,${reader.result}`);
};
reader.readAsText(event.data);
};
</script>
</body>
</html>
更多flask參考
官方文檔(中文):http://docs.jinkan.org/docs/flask/
https://blog.csdn.net/huangzhang_123/article/details/75206316
http://www.pythondoc.com/flask-mega-tutorial/
三種框架的對(duì)比參考
http://www.itdecent.cn/p/9960a9667a5c
https://www.cnblogs.com/wuzaipei/p/9694379.html