Flask項目--魚書學習筆記(一)

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關鍵字判斷一個字符串是否在另一份字符串內
  • 多個邏輯判斷排列原則:
    1. 有更大可能判斷結果爲假的條件應該放在前面
    2. 需要查詢數據庫的操作由於會消耗資源,應該儘量靠後

代碼重構

@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的設計。

將視圖函數拆分到單獨的模塊中

將試圖函數都放在一個文件中有哪些不足:

  1. 代碼太長,不利於維護
  2. 從業務模型抽象的角度,不應該把他們都放在一個文件中。關於書籍相關的API就應該放在書籍模型的視圖函數文件中,跟用戶相關的API就應該放在用戶模型相關的文件中
  3. 入口文件的意義比較獨特,會啓動web服務器以及做很多初始化的操作,就算要放在一個文件也不應該業務的操作放在入口文件中來

深入瞭解Flask路由

Flask的工作就是捕捉這個URL地址,弄清用戶想要做什麼,並在衆多的Python函數中匹配一個可以處理它的函數,請求流程如下圖:

Flask路由

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的具體流程圖

Flask執行流程

回答流程圖中的兩個問題:
問題1:因爲都是由fisher引入book,Python不會把模塊導入兩次。所以只執行了一次book。
問題2:由於一次是主流程執行fisher文件;一次是由book模塊導入 fisher。

可以看到,紅線和藍線分別註冊了兩個app,且發生了覆蓋,最終導致註冊路由和啓動的不是同一個app。
其他相關資料已經同步到我的博客網站,歡迎訪問我的個人博客瞭解更多內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章