1. 程式人生 > >Flask 實現 Rest API (02)

Flask 實現 Rest API (02)

Rest API 以 json 格式對 Request 進行響應。結果集是如何轉換成 json 呢?上一篇我們是這樣做的:

# 首先得到一個 dict
items = [dict((curr.description[i][0], value) 
    for i, value in enumerate(row)) 
    for row in curr.fetchall()]

# 然後通過 flask 的 jsonify 轉換成 json 格式
employees = emp.listAll()
return jsonify({'rows': employees}), 200;

這幾句程式碼的資訊量太大,我打算再用一篇文章來解釋,順便解決程式碼中存在的一個小問題。

獲取欄位名

Python 的 DB-API 規定,cursor 物件的 description 屬性應該返回一個序列 (sequence),這個序列包括下面的內容:

  • name
  • type_code
  • display_size
  • internal_size
  • precision
  • scale
  • null_ok

第一個元素的 name 就是我們要的 column name。據此,可以編寫一個函式得到 column name:

import pymysql

def get_connection():
    db = pymysql.connect('localhost', 'root', 'pwd',
'stonetest') return db def get_column_names(sql): conn = get_connection() cur = conn.cursor() cur.execute(sql) col_names = [] column_count = len(cur.description) for i in range(column_count): desc = cur.description[i] col_names.append(desc[0]) return
col_names col_names = get_column_names('select * from users;') print (col_names)

寫的這麼囉嗦是為了解釋 column name 是如何獲得的。通過除錯可以發現,cur.description 根據 resultset 的每一列,建立了一個 7-item 的元組 (tuple), name 處在 index 為 0 的位置。然後所有列的元組再組合成一個元組,如下圖所示:

所以我們用上面的方法,得到列名的集合,以 list 型別返回。get_column_names 函式用的是常規的迴圈方法,其實 Python 提供了很好的機制,讓我們可以大大簡化程式碼。需要用到 enumerate 函式和列表推導式。先 兩個相關相關知識做一個簡單說明。

enumerate 函式

enurmerate函式是 Python 的內建函式,enurmerate 作用於序列或者迭代器 (iterator),返回一個新的迭代器,同時自動維護一個內部的計數器,從而簡化迴圈的語句的編寫。比如從 basic_colors 的列表中,遍歷得到每一種顏色,為了迴圈,我們構造了一個計數器 i

basic_colors = ['red', 'green', 'blue']
for i in range(len(basic_colors)):
    print (i, basic_colors[i])

使用 enumerate 函式後,程式碼可以簡化為:

basic_colors = ['red', 'green', 'blue']
for i, item in enumerate(basic_colors):
    print (i, item)

列表推導式

列表推導式提供了一種簡明的方法來建立列表列表推導式在有些情況下特別方便,特別是當你需要使用 for 迴圈來生成一個新列表時。比如:

days = []
for i in range(1, 32):
    days.append(i)
print (days)

# output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]

可以使用列表推導式簡化為:

days = [i for i in range(1, 32)]
print (days)

有了以上兩個工具,get_column_names 函式可以簡化為:

def get_column_names(sql):
    conn = get_connection()
    cur = conn.cursor()
    cur.execute(sql)

    column_names = [item[0] for counter, item in enumerate(cur.description) ]
    return column_names

現在應該理解了最剛開始的 items 語句:

items = [dict((curr.description[i][0], value) 
    for i, value in enumerate(row)) 
    for row in curr.fetchall()]

翻譯一下,就是下面的程式碼:

def list_all():
    # ...

    rows = cur.fetchall()
    rows_list = []
    for row in rows:
        line_dict = dict()
        for i in range(len(row)):
            line_dict[cur.description[i][0]] = row[i]
        
        rows_list.append(line_dict)
    
    return rows_list

解決欄位順序問題

以上方法得到的 json 字串,json 字串中 key 的順序與表中欄位的順序並不一致,原因有兩點:

  • dictkey 是沒有順序的,dict 使用 key 來查詢,所以輸出的順序隨機;
  • flask 的 jsonfify() 方法在輸出欄位時,受 app.config['JSON_SORT_KEYS'] 控制,預設為 True,所以一般情況下 json 的輸出是 key 的字母順序輸出的。jsonfiy() 方法會同時將 minetype 設定為 application/json

知道問題的所在,解決辦法也很簡單:

  • app.config['JSON_SORT_KEYS'] 設為 False
  • listAll() 方法中,返回值用 dict 的子類 OrderedDict。改寫後的 listAll() 方法如下:
    def listAll(self):
        conn = db.connect()
        curr = conn.cursor()

        curr.callproc('getEmployees')

        col_names = [desc[0] for desc in curr.description]
        result = []
        for row in curr.fetchall():
            dict_obj = OrderedDict()
            for i, v in enumerate(row): # index, value
                dict_obj[col_names[i]] = v
            result.append(dict_obj)

        curr.close()
        conn.close()

        return result

References