Python Web開發之——構建基于Flask框架的web后端項目

聲明:這篇文章主要面向python/Flask/web后端初級開發者,文章主要講解了如何搭建一個基于Flask的純web后臺項目,以及相關的知識原理。不涉及部署相關操作。由于接觸web開發不久,難免會有疏漏的地方,請讀者指正。

<br />

下面是文章中會涉及到的內容:
  • HTTP請求發送到后端與響應的過程
  • Why Flask & 概覽
  • Flask的常用工具和項目配置
  • pycharm
  • HTTP requester
  • redis
  • 從第一個路由注冊接口開始
    • 使用裝飾器處理接口必填字段
    • Flask上下文獲取request參數
    • 錯誤處理
    • 數據庫連接池
    • 循環引用的那些坑
    • 封裝加密方法
    • ORM與Model
    • celery多線程、異步任務
    • 設置response模板
    • 完整的注冊接口
    • 使用Blueprint進行模塊劃分
  • 總結與展望

前言

前些日子轉了python后臺開發=。= 說實話現在對后端更有興趣。我很享受這種不管做什么都是在學習新的知識的感覺,像新生兒一樣對這個世界充滿好奇。

這篇文章總結了我最近一段時間的學習成果:使用Flask框架搭建一個可擴展的中小型web service,并在其中加上一些原理的闡述或者鏈接。在本文中以實際的用戶模塊為例。之所以寫這篇文章是因為自己在入門的時候遇到了很多坑,文檔或者個人博客并不能滿足我的需要,不是很基礎(毫無架構可言,而且大多是不實用的博客項目)就是特別復雜。在此感謝我的同學/同事evolsnow,在開發學習過程中給了我很大的幫助。也希望這篇文章能幫到想入門python/Flask的同學。

HTTP請求發送到后端與響應過程

在進行項目搭建之前,我們先大致回顧一下一個HTTP請求是如何發送至后端并且響應的。

  1. 通訊雙方進行連接
    首先通訊雙方遵從HTTP協議。當我們輸入這樣一個請求:http://www.test.com/api/info ,首先請求端會進行DNS解析,把http://www.test.com 變成一個ip地址,如果url里不包含端口號,則會使用該協議的默認端口號。通過ip地址和端口,三次握手建立一個tcp連接。
  2. 請求:連接成功建立后,開始向web服務器發送HTTP請求。Flask通過wsgi協議傳遞請求。
  3. 響應:接到請求后,交給相應路由處理,根據地址轉發給相應的控制器(函數)處理。后端大部分工作就是寫這些處理過程。處理完成后將最終的response返回給用戶。這其中我們拿到的request與response都是由python的wsgi工具包werkzeug提供的。
  4. 關閉連接:通訊雙方均可關閉socket結束tcp/ip會話。

關于wsgi:wsgi協議將處理請求的組件按照功能及調用關系分成了三種:

  • server
  • middleware
  • application。

其中,server可以調用middleware和application,middleware可以調用application。
符合WSGI的框架對于一次HTTP請求的完整處理過程為:

  • server讀取解析請求,生成environ和start_response,然后調用middleware;
  • middleware完成自己的處理部分后,可以繼續調用下一個middleware或application,形成一個完整的請求鏈;
  • application位于請求鏈的最后一級,其作用就是生成最終的響應。
如需更深入了解該過程,可以查看WSGIWerkzeug

<br />

一、Why Flask & 概覽

我負責項目web后端的用戶模塊,對并發量要求不高。考慮到Flask小巧簡單易上手,同時具有強大的擴展能力,使其功能可以不弱于django、Tornado等框架,我最終選擇了Flask。下面是Flask最簡單的一個示例,這篇文章要做的就是將其充實、擴展、拆分,使代碼具有良好的可擴展性和可讀性。

from flask import Flask
app = Flask(__name__)

@app.route('api/test')
def hello():
    return 'Hello World!'

if __name__ == '__main__':
app.run()

本文假設你已有基礎的python語法知識。裝飾器是一種代碼運行期間動態增加功能的方式,本質上是一個返回函數的高階函數,也可以簡單的將其理解為一個函數的包裝函數。上述代碼中的route方法是一個裝飾器,這個裝飾器的作用就是將地址(api/test)與方法名hello聯系起來,當HTTP請求的url為(api/test)時候將調用hello方法進行處理。也就是建立了url與處理函數的映射。深入了解可以查看這篇文章

看起來不復雜,那就讓我們繼續吧!先從環境工具配置開始:

常用工具

1.IDE

jetbrains家的pycharm,自帶終端(虛擬環境、安裝插件、啟動redis等操作)、Python Console運行python、Version Control版本控制、Even Log打印。

pycharm.png

2.請求工具

火狐瀏覽器插件—HTTP requester。可以自定義請求方式 request methods、請求內容request body、請求頭 request header等,當你寫好一個接口時,可以非常方便得進行測試。


httprequester.png

項目配置

1.虛擬環境

可以按官方文檔使用終端進行配置,也可以在pycharm的偏好設置里進行設置,這里我們使用python3.5的解釋器。安裝后執行命令進入虛擬環境,其中yourvenv為你指定創建的虛擬環境目錄

$ source yourvenv/bin/activate

2.使用pip進行包管理

pip是python的包管理工具,如果你是通過homebrew安裝python則會自動安裝pip。其他情況參考stackoverflow。安裝好pip之后,在虛擬環境中通過

$ pip install flask(庫名)

安裝Flask以及其他三方庫。

3.配置redis

Reids是現在最流行的的非關系型數據庫(key-value)。其數據緩存在內存中,因此效率很高。多用于存儲臨時的、高度動態的數據。在用戶模塊中我們將會對驗證碼進行redis存儲(短時間內進行寫入讀取操作,并且在緩存一定時間后刪除)。本文中將會已用戶注冊時生成的邀請碼為例,進行redis存取操作。
從Redis官網下載安裝包并按文檔安裝后,終端執行

$ redis-server

啟動redis,在項目中pip安裝即可調用其API。

4.項目相關約定

  • 項目采用前后端分離的方式,只進行數據交互,不使用python的jinja2模板去渲染頁面給web前端
  • web前端、iOS、Android與后端數據交互的格式均為json
  • 前端的請求頭默認帶terminal(前端類型)、version(版本號),后端返回的數據中包含“code”、“msg”參數,code=0表示請求處理成功,當code!=0時,msg為錯誤信息
  • 本文中的后端開發環境為macOS系統,使用python3.5版本

二、從第一個路由注冊接口開始

在web開發中,路由(route)是指根據url分配到對應的處理程序。
在用戶模塊中,注冊接口無疑是最基礎的。我們先來分析注冊過程:

  • 前端(包括網頁端、iOS、Android)傳過來的參數中必須包含用戶名與密碼,考慮到產品有邀請人機制,因此可能會傳邀請碼過來。因此,我們首先要做的是,判斷該接口是否傳了所必須的參數包括手機號和密碼,其次判斷邀請碼是否存在并做相應處理
  • 判斷請求中是否有邀請碼這一參數,若有則通過邀請碼從數據庫中查找邀請人。此處邀請碼及邀請人存在redis數據庫中(此處用redis只是教程需要,存入mysql即可)。若沒有,則向用戶返回錯誤碼及信息
  • 將密碼加密,存入數據庫并返回一個用戶id(默認自增,用來創建邀請碼),若失敗則返回相應錯誤
  • 若用戶在請求參數中包含“idfa”參數,則更新用戶來源渠道,該數據用于推廣運營
  • 上述過程處理完畢后,根據用戶id生成token并返回給用戶
from flask import Flask
app = Flask(__name__)

@app.route('user/register methods=['POST']')
def user_register():
    #判斷是否含有phone、password參數,若沒有則返回400錯誤
    #判斷是否有invitationCode參數,若有則從redis數據庫中獲取邀請人,若獲取不到則返回錯誤,參考REST
    #獲取phone參數,加密密碼并將用戶信息存入mysql數據,若成功則返回用戶id,失敗返回錯誤
    #根據用戶id生成邀請碼,并存入redis數據庫中
    #判斷是否有idfa參數,若有則在后臺線程在mysql中修改用戶來源(非必須同步操作,放后臺即可)
    #生成token
    return 注冊接口返回token(包含默認的code、msg)

if __name__ == '__main__':
app.run()

1. 使用裝飾器處理接口必填字段

考慮到post請求都有檢測必填參數并返回信息的需求,因此我們可以寫一個裝飾器來處理,避免每個接口都寫一大段相同的判斷代碼。裝飾器是一個返回函數的高階函數,可以在函數執行之前執行其他操作。在這里我們可以寫一個接受多個參數(即一個請求的所有必填參數)的裝飾器,如下:

def require(*required_args):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            for arg in required_args:
                if arg not in request.json:
                    return flask.jsonify(code=400, msg='參數不正確') 
           return func(*args, **kw) 
       return wrapper
    return decorator

我們只需要在請求方法之前添加裝飾器即可:

@app.route('user/register', methods=['POST'])
@require('phone','password')
def customer_register():
    xxxx
    return xxx

當請求中包含了phone和password參數時,不進行任何操作,返回原函數即user_register(),當有一個參數缺失時,直接返回錯誤信息400。注意,此處的400并不是http請求的statusCode狀態碼,而是后端與前端的一種約定,只是返回數據的其中一個參數。

其中flask.jsonfy()方法可以將key-value參數轉換為json格式。jsonify()與dump()的區別在此

為了讓項目清晰易懂,我們將此類為請求處理方法準備的裝飾器單獨放在一個文件中。在項目下創建一個python包文件夾(python package)命名為handler,創建hd_base.py文件,并將裝飾器函數寫在該文件中。當需要時,只需要引入該方法即可:

from handler.hd_base import required

看到這段代碼,大家可能會好奇,我是怎么拿到請求中的參數呢? 程序怎么知道代碼中的request就是我們現在正請求的request呢,這就涉及到了Flask的上下文。

2.Flask上下文獲取request參數

一般來講,想要拿到一個請求的內容,如header、body等,需要將這個請求當做參數傳給我們定義的user_register()函數。但是考慮到我們可能會調各種各樣的東西,為了避免大量可有可無的參數把函數搞的一團糟,Flask使用了上下文(context)臨時把某些對象變成了全局可訪問。Flask上下文分為應用上下文(AppContext)和請求上下文(RequestContext),可以簡單地理解為一個應用運行過程中/一次請求中的所有數據。在上面的裝飾器代碼中,我們用到了request.json.get()來獲取請求內容,其中request對象就是全局可訪問的。你又有疑問,在多線程服務器中,多個線程同時處理不同客戶端發送的不同請求時,每個線程拿到的request能一樣嗎?在Flask中,同一時刻一個線程只處理一個請求,使用上下文讓特定的變量在一個線程中全局可訪問,不會干擾其他線程。詳見這篇文章
要使用上下文中的request全局可訪問對象,需要引入:

from flask import request

若參數以json形式傳輸,則可通過request.json.get('phone')獲取某參數key的值,請求頭可以通過request.json.get('version')獲取,表單則可通過rquest.form獲取。

3.錯誤處理

上述代碼中,裝飾器對參數不滿足的情況返回了包含錯誤信息的json數據。那么如何直接拋錯,返回statusCode狀態碼錯誤呢,Flask為我們提供了abort方法與errohandler裝飾器。將上述裝飾器代碼稍作修改即可:

def require(*required_args):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            for arg in required_args:
                if arg not in request.json:
                    return flask.abort(400)
           return func(*args, **kw) 
       return wrapper
    return decorator

@app.errorhandler(400)
def not_found(error): 
     return make_response(flask.jsonify({'error': '參數不正確'}), 400)

當請求不包含必填參數時,請求的發送端就會收到請求失敗的信息,http狀態碼為400(請求成功的狀態碼為200,更多查看http狀態碼)。

4.數據庫連接池

項目使用了MySQL數據庫和Redis數據庫。根據不同情況選擇使用。

MySQL

pip安裝flask-sqlalchemy。sqlalchemy是實現ORM的庫,將會在下文進行介紹。按照官方文檔進行數據庫連接(此處為遠程數據庫連接):

from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://dbname:MrVg+X1ZwS4RiCh9@120.25.102.84:3306/db1'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)

當要使用數據庫時,調用db即可,下文將會結合ORM使用。

Redis

pip 安裝Redis,查閱文檔,進行連接(此處連接到本地Redis數據庫):

import redis
_redis_cache = redis.Redis(connection_pool=redis.ConnectionPool(host='127.0.0.1', port=6379, db=1))
_redis_db = redis.StrictRedis(host='127.0.0.1', port=6379, db=2)

我在這里使用兩種方式連接了Redis數據庫,其作用是相同的。其中_redis_cache用于緩存數據(如用戶驗證碼),_redis_db用做數據庫(消耗內存,但是速度快),按需使用。為了方便使用,我們將redis常用的存取方法進行封裝:

class RedisDB:
    def __init__(self, conn=_redis_db):
        self.conn = conn
    def set(self, key, value, expire=None):
        self.conn.set(key, value, expire)
    def hget(self, name, key):
        ret = self.conn.hget(name, key) 
        if ret: 
           ret = ret.decode('utf-8')
        return ret
    def hset(self, name, key, value):
        self.conn.hset(name, key, value)

class RedisCache(RedisDB):
    def __init__(self):
        super().__init__(_redis_cache)

我們在項目中創建一個python pacage命名為common,創建文件redisdb.py,將封裝的redis相關類與方法寫在其中。
當需要使用Redis時,實例化后進行操作:

rdb = RedisDB()
rdb.set('a',123)     #(key,value)
a = rdb.get('a')
print(a)             #輸出123

連接好數據庫,我們已經可以完成注冊接口的一部分代碼了:

from handler.hd_base import require
from common import redisdb
from flask import Flask
app = Flask(__name__)

rdb = redisdb.RedisDB()

@app.route('user/register', methods=['POST'])
@require('phone','password')
def customer_register():
    #邀請碼相關處理
    inviter = None
    if request.json.get('invitationCode'):
    inviter = rdb.hget('invitationCode',request.json['invitationCode'].upper())
    if not inviter:
        return flask.jsonify(error=400,msg='邀請碼無效')
    #其他處理

if __name__ == '__main__':
    app.run()

從項目文件目錄的角度來講,你可能發覺,所有的請求都寫在這個我們創建項目時的第一個文件中,有些不太合適。因此我們可以將這個文件拆分,將實例化應用、接口方法、應用啟動三個部分分離。

app/__init__.py中:

from flask import Flask
app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://dbname:MrVg+X1ZwS4RiCh9@120.25.102.84:3306/db1'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)

app/run.py中:

from app import app
if __name__ == '__main__':
    app.run()

handler/hd_user.py中:

@app.route('user/register', methods=['POST'])
@require('phone','password')
def customer_register():
     xxxxxx
     return xxxxx

創建python包(python package)為app、handler,將實例化應用代碼寫入自動生成的app包下的init.py中,當需要用到app時,引入即可。同理,在app包下創建run.py,將應用啟動代碼寫入。在handler下創建hd_user.py,寫入路由方法。

5.循環引用的那些坑

在你為了項目可讀性將代碼進行分離時,可能會遇到一個奇怪的錯誤:cannot import xxxx,無法引入某個模塊、方法或變量。如果你確認自己沒有犯一些低級錯誤,那么可能就是產生了循環引用。這也是項目初期我遇到的浪費時間最多的坑。

其實問題出在模塊導入的順序上。 比如:在A文件頭執行到語句 from B import XXX,程序馬上就會轉到B文件中去,從頭到尾順序尋找B文件中的XXX函數,而A文件就暫停執行,直到把XXX函數復制到內存中。但在這個過程中,如果B文件頭中又導入了A文件中的函數,由于XXX函數還沒有被復制,A文件就會因為暫停執行而無法導入,就會出現上面的錯誤。你可以選擇嘗試修改import順序消除循環引用,但解決這個問題最好的方法就是重新梳理邏輯,將重復使用的東西單獨提取出來的。當然,上面代碼中,將app單獨放在一個文件里定義,就是為了避免循環引用,每個文件需要app對象時,單獨import它即可。

6.封裝加密方法

對密碼進行md5加鹽加密是一種常見的安全手段。MD5加密算法是不可逆的,如果需要驗證密碼是否正確,需要對待驗證的密碼進行同樣的MD5加密,然后和數據庫中存放的加密后的結果進行對比。但是MD5加密依然不夠安全,因此我們在此基礎上采用加鹽加密
在common包中新建security.py文件,封裝加密方法:

#md5加鹽加密
def _hashed_with_salt(info, salt):        
    m = hashlib.md5()
    m.update(info.encode('utf-8'))
    m.update(salt)
    return m.hexdigest()

#對登錄密碼進行加密
def hashed_login_pwd(pwd):      
    return _hashed_with_salt(pwd, const.login_pwd_salt)

之所以將加鹽加密方法單獨拿出來,是因為項目后期可能會對其他信息進行加密,比如交易密碼。到時復用加密方法,修改參數即可,可以保證代碼簡潔。通過如下代碼即可對用戶登錄密碼進行加密:

from common import security
password = security.hashed_login_pwd(request.json['password'])

7.ORM與Model

ORM全稱Object Relation Mapping,即對象關系映射。用于實現面向對象編程語言里不同類型系統的數據之間的轉換。簡單來講,就是你不再需要寫sql語句對數據庫進行CRUD操作,只需建立每張表對應的模型Model類,調用相應方法即可達到相同的效果。
以用戶表為例,我們新建Model包,創建user.py,添加user模型:

from app import db
from common import utils
from model import userconst

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80),unique=False)
    phone = db.Column(db.String(80), unique=True)
    password = db.Column(db.String(80),unique=False)
    source = db.Column(db.Integer,unique=False)
    terminal = db.Column(db.Integer,unique=False)
    invited_from = db.Column(db.String(80),unique=False)

    #重寫該方法,方便輸出user信息
    def __repr__(self):
        user = ''
        user += 'name: %s\n' % (self.name)
        user += 'phone: %s\n' % (self.phone)
        user += 'password: %s\n' % (self.password)
        ········
        return user

def create_user(phone,password,invited_from,terminal):
    user = User(phone=phone,password=password,source=userconst.SOURCE_DEFAULT,
                  terminal=terminal,invited_from=invited_from)
    db.session.add(user)
    try:
        db.session.commit()
    except BaseException:
        return 0
    else:
        return user.id    

其中userconst.py 定義了一些user相關的常量。

我們定義了User類,并定義了一個創建用戶的方法。若數據庫添加用戶成功,則返回默認自增id,若有任何異常錯誤,返回0,代表創建用戶失敗,即注冊失敗。

本例我們使用ORM代替了sql語句"INSERT INTO user VALUES (phone, name,....)"。
現在,讓我們將注冊路由的方法更進一步。在user_register方法中添加如下代碼:


·········
def customer_register():
    #邀請碼相關操作,已省略
    ········
    phone = request.json['phone']
    #對密碼進行MD5加鹽加密
    password = security.hashed_login_pwd(request.json['password'])
    #寫入數據庫并處理返回
    uid = create_user(phone,password,inviter,request.headers['terminal'])
    if uid == 0:
        return error_resp(500,'注冊失敗,請核對信息后重新輸入')
    #其他處理
········


了解了ORM后,我們可以試著從數據庫中獲取手機號為“18012341234”的用戶的姓名并修改:

user = db.session().query(User).filter_by(phone='18012341234').first()
print(user.name)     #輸出姓名
user.name = '張三'
db.session.add(user)
db.session.commit()    #提交修改

可以看到,簡單、精確、易用是ORM的特點,但是ORM映射會消耗內存,當數據變得復雜且龐大時,使用ORM會帶來不小的性能損耗,我們要根據實際情況進行選擇。

8.celery多線程、異步任務

Celery是一個異步任務隊列,一個基于分布式消息傳遞的作業隊列。它支持使用任務隊列的方式在分布的機器、進程、線程上執行任務調度。總的想法就是你的應用程序可能需要執行任何消耗資源的任務都可以交給任務隊列,讓你的應用程序自由和快速地響應客戶端請求。

在handler中新建tasks.py,配置celery,添加相關方法:

from celery import Celery

def make_celery(app):
    celery = Celery(app.import_name, broker=app.config['redis://localhost'])
    celery.conf.update(app.config)
    TaskBase = celery.Task
    class ContextTask(TaskBase):
        abstract = True
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return TaskBase.__call__(self, *args, **kwargs)
    celery.Task = ContextTask
    return celery

celery = make_celery(app)

當我們需要實現后臺任務時,只需要在方法前添加celery的裝飾器。將下列代碼寫入tasks.py中:

@celery.task
def update_user_source(ifa, phone):
    records = db.session().query(Advertisement).filter_by(ifa=ifa).all()
    if len(records) == 0:
        return
    selected = sorted(records, key=lambda x: x.create_time, reverse=True)[0]
    source = Advertisement.AD_SOURCE_MAP[selected.ad_service]
    user = db.session().query(Customer).filter_by(phone=phone).first()
    user.source = source
    db.session.add(user)
    db.session.commit()

在終端中啟動celery worker

$ celery -A tasks worker

在注冊接口中,我們可以在用戶數據插入成功之后,后臺更改用戶的source來源:

def customer_register():
    ········
    idfa = request.headers.get('idfa')
    if idfa:
        update_user_source.delay(idfa,phone)
        rdb.hset(const.user_idfa,uid,idfa)
    ········

其中const.py在common包下,存放字符串常量。
delay() 方法是強大的 apply_async() 調用的快捷方式。這樣相當于使用 apply_async():

update_user_source.apply_async(args=[idfa,phone])

當使用 apply_async(),你可以給 Celery 后臺任務如何執行的更詳細的說明。一個有用的選項就是要求任務在未來的某一時刻執行。例如,這個調用將安排任務運行在大約一分鐘后:

update_user_source.apply_async(args=[idfa,phone], countdown=60)

9.設置response模板

按照我們本項目的約定,接口返回的數據中帶code和msg字段,當code為0說明請求成功,msg為空;當code不為0時,msg為錯誤信息。在handler中創建template.py:

import flask

_base_dic = {
    'code':0,
}

def error_resp(code,msg):
    return flask.jsonify(error=code,msg=msg)

def register_teml(token):
    return dict({
        'token':token,
    },**_base_dic)

設置注冊接口的response:

def user_register()
    #所有處理
    ············
    return flask.jsonify(**template.register_teml(token=security.generate_token(uid)))

10.完整的注冊接口

至此,注冊接口我們就完成了。完整的路由方法如下:

@app.route('user/register', methods=['POST'])
@require('phone','password')
def customer_register():
    #邀請碼相關處理
    inviter = None
    if request.json.get('invitationCode'):
    inviter = rdb.hget('invitationCode',request.json['invitationCode'].upper())
    if not inviter:
        return flask.jsonify(error=400,msg='邀請碼無效')
    phone = request.json['phone']
    #對密碼進行MD5加鹽加密
    password = security.hashed_login_pwd(request.json['password'])
    #寫入數據庫并處理返回
    uid = create_user(phone,password,inviter,request.headers['terminal'])
    if uid == 0:
        return error_resp(500,'注冊失敗,請核對信息后重新輸入')
    #用戶來源處理
    idfa = request.headers.get('idfa')
    if idfa:
        update_user_source.delay(idfa,phone)
        rdb.hset(const.user_idfa,uid,idfa)
     return flask.jsonify(**template.register_teml(token=security.generate_token(uid)))

現在,一切看起來都很順利。但是當用戶模塊的接口添加了登陸、驗證碼相關、重置密碼、獲取用戶信息等接口后,hd_user會變得很大,這時再添加其他模塊的接口時,文件就會變得難以維護。而且當你嘗試著新建一個文件寫其他模塊的接口時,會產生循環引用、代碼重復、耦合性太強等等問題。這還僅僅是純后端,不涉及用模板渲染html頁面。這時我們應該就想到將項目模塊化,在Flask中Blueprint很好的幫我們解決了這個問題。

10.使用Blueprint進行模塊劃分

Blueprint通常譯作藍圖或藍本。藍本允許你將不同路由分開,提供一些規范標準,并且附帶了很多好處:讓程序更加松耦合,更加靈活,增加復用性,提高查錯效率,降低出錯概率。如果你是純萌新,沒有大中型項目經驗,對藍圖可能會理解困難。我在知乎找到一篇回答,從『小白』和『專業』兩個方面解釋了藍圖是什么,大家可以去看看:如何理解Flask中的藍本?

Blueprint使用也非常簡單。從避免循環導入、減少代碼耦合的角度看,我們在app包下創建bpurls.py,注冊藍圖:

from flask import Blueprint

userBP = Blueprint('user', __name__,url_prefix='/user')
productBP = Blueprint('product', __name__,url_prefix='/product')

每個藍圖代表一個模塊,可以設置前綴。在hd_user中導入userBP,此時對方法進行修改:

#@app.route('user/register', methods=['POST'])
@userBP.route('register', methods=['POST'])
def customer_register():
      ·······

同理,當你寫product模塊相關接口時,創建hd_product.py,導入productBP即可。
藍本在使用模板的項目中用處更加突出,有興趣的同學可以查閱相關文檔

總結及展望

這篇文章我最近一段時間的項目實踐總結。雖然內容很簡單,但是由于自己知識漏洞較多,還是學到了不少新知識。在寫文章的時候查閱了很多資料,眼界也更加開闊。雖說iOS還未精通,python web也才入門,但是我對其他技術也充滿好奇。下一步可能會在下班空閑時間深入已有知識體系的同時,學習下數據相關的技術。總之,stay hungry ,stay fool !

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 22年12月更新:個人網站關停,如果仍舊對舊教程有興趣參考 Github 的markdown內容[https://...
    tangyefei閱讀 35,231評論 22 257
  • # Python 資源大全中文版 我想很多程序員應該記得 GitHub 上有一個 Awesome - XXX 系列...
    aimaile閱讀 26,578評論 6 427
  • GitHub 上有一個 Awesome - XXX 系列的資源整理,資源非常豐富,涉及面非常廣。awesome-p...
    若與閱讀 18,757評論 4 418
  • 環境管理管理Python版本和環境的工具。p–非常簡單的交互式python版本管理工具。pyenv–簡單的Pyth...
    MrHamster閱讀 3,853評論 1 61
  • 對視的那一剎那 我讀懂了自己為愛的付出 卻讀不懂你 每一次輕聲的問候 那掩飾的隨意中 都蘊積了我千個日夜的乞盼 看...
    從心活過閱讀 341評論 3 9