Flask項目–魚書學習筆記(一)
Flask安裝過程略。
判斷查詢參數是否爲isbn號
@app.route("/search/<q>/<page>")
def search(q,page):
"""
搜索書籍路由
:param q: 關鍵字 OR isbn
:param page: 頁碼
"""
# isbn isbn13 由13個0-9在數字組成
# isbn10 由10表0-9表數字組組成,中間可能包含' - '
isbn_or_key = 'key'
if len(q) == 13 and q.isdigit():
isbn_or_key = 'isbn'
short_q = q.replace('-', '')
if '-' in q and len(short_q) == 10 and short_q.isdigit():
isbn_or_key = 'isbn'
pass
知識點:
- 字符串中的**isdigit()**函數可以判斷是否爲數字
- in關鍵字判斷一個字符串是否在另一份字符串內
- 多個邏輯判斷排列原則:
- 有更大可能判斷結果爲假的條件應該放在前面
- 需要查詢數據庫的操作由於會消耗資源,應該儘量靠後
代碼重構
@app.route("/search/<q>/<page>")
def search(q, page):
"""
搜索書籍路由
:param q: 關鍵字 OR isbn
:param page: 頁碼
"""
isbn_or_key = is_isbn_or_key(q)
helper.py
def is_isbn_or_key(word):
"""
判斷word是isbn號還是查詢關鍵字key
isbn isbn13 由13個0-9在數字組成
isbn10 由10表0-9表數字組組成,中間可能包含' - '
:param word:
:return: key or isbn
"""
isbn_or_key = 'key'
if len(word) == 13 and word.isdigit():
isbn_or_key = 'isbn'
short_word = word.replace('-', '')
if '-' in word and len(short_word) == 10 and short_word.isdigit():
isbn_or_key = 'isbn'
return isbn_or_key
如何判斷代碼是否需要封裝
- 視圖函數中不要有太多數據處理、判斷類型的代碼,能封裝的儘量封裝
- 是否有大量判斷類型的代碼
- 代碼能否重用
- 是否可以一眼看出這段代碼的作用
知識點:
- 看源代碼分層的看,第一遍首先看懂代碼的作用,理清代碼的結構線索,而不是一定要看代碼的實現細節。
- 過多的註釋會讓代碼變的臃腫,儘量使用易懂的函數名來代替註釋,保持代碼的簡潔性。
使用三元表達式優化
import requests
class HTTP: #class HTTP(object):有無都無所謂,在Python3中都表示新式類
@staticmethod
def get(url,return_json=True):
r = requests.get(url)
if r.status_code == 200:
if return_json():
return r.json()
else:
return r.text
else:
if return_json:
return {}
else:
return ''
優化後
import requests
class HTTP: #class HTTP(object):有無都無所謂
@staticmethod
def get(url,return_json=True):
r = requests.get(url)
if r.status_code != 200:
return {} if return_json else '' #特例情況
return r.json() if return_json else r.text #正常返回
知識點:
- @staticmethod註解表示這是一個靜態方法,@classmethod也可達到相同效果,不過需要傳一個cls參數
- 最好將正常情況下的return放在最後
將具體調用HTTP請求,獲取結果的業務代碼封裝
class YuShuBook:
search_by_isbn_url = "http://t.yushu.im/v2/book/search/isbn/{}"
search_by_key_url = "http://t.yushu.im/v2/book/search?q={}&count={}&start={}"
@classmethod
def search_by_isbn(cls, isbn):
url = cls.search_by_isbn_url.format(isbn)
return HTTP.get(url)
@classmethod
def search_by_key(cls, q, count=15, start=0):
url = cls.search_by_key_url.format(q, count, start)
return HTTP.get(url)
用jsonify代替json.dumps
使用前:
@app.route("/book/search/<q>/<page>")
def search(q, page):
"""
搜索書籍路由
:param q: 關鍵字 OR isbn
:param page: 頁碼
"""
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'isbn':
result = YuShuBook.search_by_isbn(q)
else:
result = YuShuBook.search_by_key(q)
return json.dumps(result), 200, {'content-type': 'application/json'}
使用後:
return jsonify(result)
獲取http請求,返回json格式的數據,其實就是API的開發,API開發的難點在於url的設計。
將視圖函數拆分到單獨的模塊中
將試圖函數都放在一個文件中有哪些不足:
- 代碼太長,不利於維護
- 從業務模型抽象的角度,不應該把他們都放在一個文件中。關於書籍相關的API就應該放在書籍模型的視圖函數文件中,跟用戶相關的API就應該放在用戶模型相關的文件中
- 入口文件的意義比較獨特,會啓動web服務器以及做很多初始化的操作,就算要放在一個文件也不應該業務的操作放在入口文件中來
深入瞭解Flask路由
Flask的工作就是捕捉這個URL地址,弄清用戶想要做什麼,並在衆多的Python函數中匹配一個可以處理它的函數,請求流程如下圖:
Endpoint有什麼作用
端點通常用作反向查詢URL地址(viewfunction**–>endpoint–>**URL)。例如,在flask中有個視圖,你想把它關聯到另一個視圖上(或從站點的一處連接到另一處)。不用去千辛萬苦的寫它對應的URL地址,直接使用URL_for()
就可以啦:
@app.route('/')
def index():
print url_for('give_greeting', name='Mark') # 打印出 '/greeting/Mark'
@app.route('/greeting/<name>')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
這樣做是大有裨益的:我們可以隨意改變應用中的URL地址,卻不用修改與之關聯的資源的代碼。
爲何要多此一舉
那麼問題來了:爲何要多此一舉,爲何要先把URL映射到端點上,再通過端點映射到視圖函數上,爲何不跳過中間的這個步驟?
原因就是採用這種方法能夠使程序更高、更快、更強。例如藍本。藍本允許我們把應用分割爲一個個小的部分,現在admin藍本中含有超級管理員級的資源,user藍本中則含有用戶一級的資源。
藍本允許咱們把應用分割爲一個個以命名空間區分的小部分:
main.py:
from flask import Flask, Blueprint
from admin import admin
from user import user
app = Flask(__name__)
app.register_blueprint(admin, url_prefix='admin')
app.register_blueprint(user, url_prefix='user')
admin.py:
admin = Blueprint('admin', __name__)
@admin.route('/greeting')
def greeting():
return 'Hello, administrative user!'
user.py:
user = Blueprint('user', __name__)
@user.route('/greeting')
def greeting():
return 'Hello, lowly normal user!'
注意,在兩個藍本中路由地址*‘/greeting’的函數都叫"greeting"
。如果我想調用admin對應的greeting
函數,我不能說“我想要greeting*”,因爲這裏還有一個user對應的greeting
函數。端點這時就發揮作用了:指定一個藍本名稱作爲端點的一部分–通過這種方式端點實現了對命名空間的支持。所以,我們可以這樣寫:
print url_for('admin.greeting') # Prints '/admin/greeting'
print url_for('user.greeting') # Prints '/user/greeting'
實例
from flask import Flask, url_for
app = Flask(__name__)
# We can use url_for('foo_view') for reverse-lookups in templates or view functions
@app.route('/foo')
def foo_view():
pass
# We now specify the custom endpoint named 'bufar'. url_for('bar_view') will fail!
@app.route('/bar', endpoint='bufar')
def bar_view():
pass
with app.test_request_context('/'):
print (url_for('foo_view')) #/foo
print (url_for('bufar')) #/bar
# url_for('bar_view') will raise werkzeug.routing.BuildError
print (url_for('bar_view')) #端點bar_view是沒有定義的
循環引入流程分析
下面看下fisher.py和book.py的具體流程圖
回答流程圖中的兩個問題:
問題1:因爲都是由fisher引入book,Python不會把模塊導入兩次。所以只執行了一次book。
問題2:由於一次是主流程執行fisher文件;一次是由book模塊導入 fisher。
可以看到,紅線和藍線分別註冊了兩個app,且發生了覆蓋,最終導致註冊路由和啓動的不是同一個app。
其他相關資料已經同步到我的博客網站,歡迎訪問我的個人博客瞭解更多內容。