Flask編程總結-魚書項目

一. flask基本原理

視圖函數

fisher.py

定義視圖函數©

通過裝飾器來給函數定義一個路由,從而可以通過http請求來訪問到這個函數;

基於函數的視圖很難去實現代碼的複用;

from flask import Flask
app = Flask(__name__)
@app.route('/hello')
def hello()
    return 'hello'
app.run()

唯一url原則

需要保證唯一url,不加/也可以保證唯一url,但是不兼容用戶的輸入,所以可以在規則中兼容用戶的輸入:

@app.route('/hello/')

新更改後的代碼必須重啓下服務器,將新更改的代碼生效;

唯一url原則的本質-重定向

兼容帶不帶/的原理:處理不同的url的實質是做了一次重定向,重定向的原理是瀏覽器與服務器之間的訪問:

  1. 在正常的訪問流程中,瀏覽器訪問url1訪問的資源,服務器在接受到請求後,把url1的資源返回到瀏覽器,並且將狀態碼設置爲200;

    1. 但在重定向過程中,瀏覽器訪問url1的資源,出於某種原因,服務器沒有url1或者不想讓你訪問url1,此時會在服務器返回給瀏覽器的信息中的location字段設置爲url2的地址,並且把返回的狀態碼改爲301或者302,到瀏覽器接受時,會根據狀態碼來獲取重定向url的信息,並再次向url2發送請求。

開啓flask服務器自動重啓

將flask的模式設置爲調試模式,就可以使flask服務器開始自動重啓。打開調試模式的方法就是在app.run的方法內傳入debug參數,並將其設置爲true:

app.run(debug=True)

開啓調試模式的另一個好處是其可以把運行的異常來進行訪問;

路由註冊

方法一通過裝飾器來註冊(裝飾器不用添加view_func參數)(推薦使用)

@app.route('/hello')
def hello()
 return 'hello'

方法二通過app的方法來進行註冊(如果是使用即插視圖,那麼就要使用add的方式來進行註冊)

def hello()
 return 'hello'
# 第一個參數指定路由鏈接,第二個參數指定視圖函數
app.add_url_rule('/hello',view_func=hello)

兩種方法的區別就是通過裝飾器方法來進行路由註冊無需指定view_func,因爲裝飾器本身就是定義在要裝飾的函數的前面。如果是使用基於類的視圖,即即插視圖的方法,則需要使用add_url_rule來進行註冊。

app.run的相關參數

app.run默認服務器運行的,只能是本地或局域網內的機器能夠訪問,可以通過app.run方法中的host屬性,來對外網中的url進行訪問。

# host如果定爲固定的ip地址,則只有那一個可以訪問
app.run(host='0.0.0.0',debug=True,port=81)

flask配置文件

部署的最基本原則,就是要保證我們開發環境下的源代碼和生產環境下的源代碼一定要是鏡像關係,即兩份源代碼一樣。

這時的解決方法就是配置文件config.py:

DEBUG = True

方法一:使用配置文件時,將其當作模塊導入進來即可:

from config import DEBUG
app.run(host='0.0.0.0',debug=DEBUG,port=81)

方法二:使用app方法來進行導入:

# 配置文件載入:方法需要接受一個模塊的路徑
app.config.from_object('config')
# 配置文件讀取:
# config本身就是字典dict的一個子類
app.run(host='0.0.0.0',debug=app.config['DEBUG'],port=81)

注:如果是採用from_object來載入的話,要求DEBUG需要大寫。但是如果你在配置文件中沒有對DEBUG參數進行定義時,運行也不會報錯,因爲DEBUG在flask中的默認值爲false。

if__name__ == 'main’的含義:

在開發環境下,運行的服務器是flask自帶的非常簡單的服務器,但是將項目部署到生產環境時,我們通常是不會使用flask自帶的服務器的,通常使用nginx+uwsgi,nginx作爲前置服務器用來接受瀏覽器發來的請求,接着會將請求轉發給我們的uwsgi。在項目中,不是通過run直接執行程序,而是通過uwsgi加載fisher模塊來啓動flask的相關代碼。

if __name__ == '__main__':
               app.run(host='0.0.0.0',bebug=app.config['DEBUG'],port=81)

在生產環境下fisher文件不再是入口文件,它只是被uwsgi加載的模塊文件,所以在生產環境下,app.run是根本不會被執行的。如果沒有了name=main的判斷,在生產環境下一旦加載了fisher文件之後,app.run就會執行,我們已經啓動了uwsgi作爲我們的服務器,接着我們又啓動了app.run作爲我們的內置服務器,兩個服務器是不可以的。但是加入了main之後,我們可以保證在生產環境下不會啓動flask自帶的web服務器。(測試環境下運行才能進入到main函數)

響應對象Response

視圖函數的return和普通函數的return有什麼區別嗎?

視圖函數的return,flask會在其背後做一系列的封裝,即除了返回定義的字符串的內容之外,還會返回status code,content-type;

content-type是放置於http的headers的屬性中,content-type告訴http請求的接收方該如何解析我們的主體內容。

對於視圖函數,content-type默認值爲"text/html",其會指導瀏覽器將html標籤當作html來進行解析。

當你在視圖函數中返回一個很簡單的字符串時,flask會將字符串當作響應的主體內容,同時把主題內容封裝爲Response對象,所以對於視圖函數而言,返回的永遠都是一個Response對象。

from flask import Flask,make_response
def hello():
    # 修改content-type讓其顯示
    headers = {
        'content-type':'text/plain'
    }
    response = make_response('<html></html>',404)
    response.headers = headers
    return response

web返回的本質都是字符串,其控制的本質是content-type;

也可以不去自己創建response對象:

from flask import Flask,make_response
def hello():
    return '<html></html>',301

二. 數據與flask路由

基本目標與功能

目標是完成:作爲一個贈送書的人,你到底要贈送哪本書;

書籍搜索的功能包括兩部分,一個是關鍵字搜索,另一個是根據isbn號進行檢索。而書籍的書籍信息不是本地的數據源,而是依賴於外部的數據源,通過外部的API來獲取數據。

考慮客戶端傳遞參數

需要傳遞什麼參數

思考下客戶端需要傳遞給我們什麼樣的參數,客戶端傳遞給我們的參數最終要使用到外部的API,所以首先要看外部的API需要接受什麼樣的參數。

那麼在這裏,關鍵字搜索的API接口爲:

http://t.yushu.im/v2/book/search?q={}&start={}&count={}

isbn搜索的接口爲:

http://t.yushu.im/v2/book/isbn/{isbn}

區分不同的搜索接口的方法

瞭解過接口後,那是否需要用戶來通過區分不同的搜索呢?即用戶主觀選擇使用isbn或關鍵字搜索後,再輸入進行搜索。顯然這樣是不必要的,我們可以通過自己內部的邏輯判斷處理,統一化搜索接口。

在視圖函數中接受用戶傳來的參數

可以先通過最簡單的方式,即通過url路徑的方式來進行參數傳遞,通過在路由中定義<>的形式,就可以將內容識別爲參數而不是固定的字符串。

注:在書寫多條件的語句時,有兩個原則:第一個原則是:應該把很大概率出現假的條件放在前面,這樣一旦前面判斷出是假的之後,後面的判斷就不會再執行了,從而讓代碼執行效率加快;第二個原則是:把比較耗時的操作放在後面;

@app.route('/book/searc h/<q>/<page>')
def search(q,page):
    # q:普通關鍵字,isbn關鍵字
    # page
    #isbn有isbn13和isbn10兩種方式:13表示由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 

代碼重構

上述代碼不好的地方:

  1. 條件邏輯判斷語句太多;

  2. 無法實現複用;

  3. 通常讀代碼都是從控制器或者是視圖函數開始的,所以視圖函數或控制器應該把這塊是做什麼的體現出來;

那麼比較好的解決方法就是將這些邏輯功能風爪給你爲一個函數,然後在視圖函數中調用它。

在包中新建模塊helper.py文件,構造方法:

def is_isbn_or_key(word)
    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'
    return isbn_or_key

然後在fisher.py文件中調用這個邏輯函數:

from helper import is_isbn_or_key

視圖函數是開始一個web項目的起點,所以一定要保證視圖函數裏的代碼是簡潔宜讀的。

看源代碼要分層的去看,第一層就是知道函數做什麼就夠了,理清楚整個源代碼的結構和線索。

requests發送http請求及代碼簡化手段

在py中訪問API,即向url發送http請求,我們可以定義http模塊,在模塊中封裝類來實現這個功能。
魚書的API還是按照restful的標準做的API,凡是restful的API都遵循着一定的規律。
在同模塊下定義http.py文件

from urllib import request
import requests
class HTTP:
    def get(self, 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 ''

簡化寫法:

class HTTP:
    def get(self, url, return_json=True):
        r = requests.get(url)
        if r.status_code != 200:
            return {} if rerurn_json else ''
        return r.json() if return_json else r.text

不要直接直白的去寫if-else,這樣會讓代碼顯得過於冗長,總結下簡化if-else的方法:

  1. 利用三元表達式來簡化if-else的寫法;

  2. 使用if+return來簡化代碼的寫法;

    if+2個return中後面的return可以理解爲正常流程中的一種特例處理情況;

在視圖函數中調用http請求類

在視圖函數中平鋪的寫業務邏輯代碼並不是一個很好的習慣,所以可以把整個的請求過程封裝爲一個類:在fisher包下新建一個模塊文件yushu_book.py,並建新類YuShuBook,接着把業務邏輯封裝到這個類中。
因爲查詢時有關鍵字查詢和isbn查詢,而這兩個查詢的方法又都不一樣,因此我們可以定義兩個方法來完成。
既然定義了一個類,那麼它就有職責自身去完成API的請求,所以對方法來傳入參數並不是一個很好的做法(search_by_isbn(self,url)不好)。那麼我們就可以把url定義在類屬性或者是類變量裏進行處理,在這裏如果我們用實例方法來進行考慮,則self這個參數本身沒有意義,因此我們可以將其改爲靜態方法。

from http import HTTP
class YuShuBook:
    isbn_url = 'http://t.yushu.im/v2/book/isbn/{}'
    keyword_url = 'http://t.yushu.im/v2/book/search?q={}&start={}&count={}'
    @classmethod
    def search_by_isbn(cls, isbn):
        # 通過類名訪問類變量
        url = YuShuBook.isbn_url.format(isbn)
        # 定義爲類方法後,就可以通過cls來訪問方法或變量
        url = cls.isbn_url.format(isbn)
        result = HTTP.get(url)
        return result
    @classmethod
    def search_by_keyword(cls,keyword,count=15,start=0):
        url = YuShuBook.keyword_url.format(keyword,count,start)
        result = HTTP.get(url)
        return result    

接下來在視圖函數中使用我們封裝的兩個方法:
(通過alt+enter鍵來進行快速導入提示的包)

def search(q,page):
    isbn_or_key = is_isbn_or_key(q)
    if isbn_or_key == 'isbn':
        result = YuShuBook.search_by_isdn(q)
    else:
        result = YuShuBook.search_by_keyword(q)
        # dict序列化
       return json.dumps(result),200,{'content-type':'application/json'} 

jsonify

前面在傳遞json的內容比較冗長,這裏我們用flask特定的jsonify的方法來對json的內容進行處理,這有點類似於API的功能:把數據通過json格式返回到客戶端去。

return jsonify(result)

將視圖函數拆分到單獨的文件中

不推薦把所有視圖函數放在一個文件中:

  1. 放到一個文件中,代碼太長,不容易維護;
  2. 從業務模型的角度來講,不同的業務模型應該分配到不同的文件中去;

現在我們來設計下文件的目錄結構:
-fisher
fisher.py
– app
— web
---- book.py
接着把視圖函數移到我們的web目錄下面來,對於web.py:

@app.route('/book/searc h/<q>/<page>')
def search(q,page):
    isbn_or_key = is_isbn_or_key(q)
    if isbn_or_key == 'isbn':
        result = YuShuBook.search_by_isdn(q)
    else:
        result = YuShuBook.search_by_keyword(q)
    # dict序列化
    return jsonify(result)

那麼現在有一個問題,我們如何把核心對象app導入到book這個文件來使用呢?
我們回顧下之前的思路:之前把視圖函數放在fisher,py這個啓動文件中,然後我們在啓動文件裏實例化了flask核心對象,接着就可以直接在下面使用我們的核心對象。但是現在因爲移動了文件,沒有辦法再直接使用核心對象了,那麼接下來我們的考慮是:

  1. 把啓動文件的核心對象導入到book中來使用(結果->會報404錯誤)
    但是這裏一定要清楚的一點是,我們是以fisher.py作爲啓動文件的,而不是book.py文件,那麼在book中引用核心對象,只能保證book中關聯了fisher,但是在fisher運行時,book文件依然會得不到執行。所以執行後仍然會報404錯誤。
  2. 再把視圖函數導入啓動文件的模塊(結果->依然404錯誤)
    在fisher.py中實例化flask對象之後加上語句:from app.web import book,啓動文件確實會運行了視圖函數,但是仍然會報404錯誤。
# fisher.py
from flask import jsonify
from helper import is_isbn_or_key
from yushu_book import YuShuBook
from fisher import app

app = Flask(__name__)
app.config.from_object('config')

from app.web import book

if __name__ == '__main__':
	print(id(app))
    app.run(host='0.0.0.0', debug=app.config['DEBUG'], port=81)

清楚這個404的錯誤問題,我們需要對flask路由機制有一個深入的瞭解:
首先,對於客戶端(或說postman),我們是如何通過url地址,最終訪問到Search這個視圖函數的。很容易想到的就是通過字典來映射,即字典的key表示的是url,字典的value表示的是視圖函數。
而flask除了url和對應的視圖函數之外,中間還有一個endpoint。endpoint是用來反向構造url的。
如何才能算成功的註冊路由呢?首先urlmap,這個map對象中必須有我們的search的一 個指向,同時view-functions必須要記錄search on point它所指向的一個視圖函數。這樣當一個請求進到flask的內部,它才能通過url最終找到我們的視圖函數,從而調用視圖函數。
出現第二個404錯誤的原因在於,我們在調用時是一個循環引用。

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