Python Web之Flask

安裝

需要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_urlhttp://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è)版塊:mainmain2,然后其他文件繼承他,并在這些版塊里寫內(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)

參考:http://www.manongjc.com/detail/8-fjqznbdwcmthgru.html

查詢數(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.permanentTrue即可,默認(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ā),使用步驟如下:

  1. 創(chuàng)建藍(lán)圖
  2. 編寫藍(lán)圖下視圖函數(shù)
  3. 注冊(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()
跨域處理

https://www.cnblogs.com/wuzaipei/p/9694379.html

流內(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容