python筆記4:編寫web框架支持路由與mysql

Table of Contents

01有參數的裝飾器

02實現自動路由

03裝飾器實現路由

04捕捉異常

05路由概念

06靜態、動態、僞靜態url

07支持僞靜態

08 支持mysql

09個人中心頁面數據


01有參數的裝飾器

閉包:擁有一個函數(返回內部函數的引用),與它獨有的數據空間(一般函數沒有)。並且比全局變量更封閉

返回內部函數引用時不帶括號,因爲帶括號是使用(類創建實例,函數執行),不帶括號是指針

裝飾器:怎麼調用原函數,怎麼調用閉包內部函數(參數一致)。通用裝飾器一般設置參數爲*args,**kwargs,並在調用原函數傳值時再次*args,**kwargs拆包,並return原函數返回值

多個裝飾器對同一個函數裝飾:從下往上裝飾,從上往下遞歸調用執行

需求:不同的函數權限驗證的級別不同

#法1

def set_func(func):

       def call_func(*args,**kwargs):

              level=args[0]

              if level==1:

                     print("權限驗證級別1")

              if level==2:

                     print("權限驗證級別2")

              return func()

       return call_func

 

@set_func

def test1():

       print("test")

       return "ok"

 

@set_func

def test2():

       print("test")

       return "ok"

test1(1)

test2(1)

缺點:

  1. 裝飾器一般不能影響原函數調用,定義函數時不用參數,調用時就不應該需要參數。而這個參數是給裝飾器傳的,不是給原函數傳的。如果函數多次調用,則每次都需要給函數傳對應裝飾器需要的參數。
  2. 裝飾器需要驗證的級別應該是在裝飾函數時規定好的,如果在調用函數時自己傳該參數,可自定義驗證級別,不能有效驗證

帶參數裝飾器:

def set_level(level_num):#1.接收1,定義函數

       def set_func(func):#3.用上一步指針調用裝飾

              def call_func(*args,**kwargs):

                     if level_num==1:

                            print("權限驗證級別1")

                     if level_num==2:

                            print("權限驗證級別2")

                     return func()

              return call_func

       return set_func#2.返回第二層函數

 

#調用set_level並將1當作實參傳遞,獲得返回值

#用上一步的返回值set_func當作裝飾器對test1裝飾(需要多一層函數)

@set_level(1)

def test1():

       print("test")

       return "ok"

 

@set_level(2)

def test2():

       print("test")

       return "ok"

 

test1()#4.最後還是調用call_func

test2()

與方案1區別:

加了裝飾器之後,調用不變,不用把以前所有的調用都加上參數

調用者無權設置驗證級別

02實現自動路由

已知:web服務器已經將請求通過字典的方式傳遞到框架的application函數中。該函數決定返回值

當前是檢查url,動態資源在函數中一一對應要執行的函數

def application(env, start_response):

    start_response('200 OK', [('Content-Type', 'text/html')])

    file_name=env['PATH_INFO']

    if file_name=='/index.py':

           return index()

    elif file_name=='/center.py':

           return center()

    return '<h1>Hello, web!</h1>'

判斷file_name調用函數組裝字符串,區分url請求的資源是什麼,封裝固定代碼

需求:真實網站有各種功能,不可能全部手寫判斷。利用字典思想,選擇不同key可以得到不同value,根據不同請求讓它自動調用相應函數。可以在value中存函數的引用

全局變量一般使用g_開頭或大寫命名

#法1

URL_FUNC_DICT={

       "/index.py":index,

       "/center.py":center,

}

def application(env, start_response):

    start_response('200 OK', [('Content-Type', 'text/html')])

    file_name=env['PATH_INFO']

    func=URL_FUNC_DICT[file_name]

    return func()

    return '<h1>Hello, web!</h1>'

優點:調用時更簡潔

缺點:每次都寫字典很麻煩,最好自動生成字典

03裝飾器實現路由

定義一個空字典,讓裝飾器往字典中加值

import re

URL_FUNC_DICT=dict()

 

def route(url):#url="/index.py"

       def set_func(func):#func=index引用

              URL_FUNC_DICT[url]=func

              def call_func(*args,**kwargs):

                     return func(*args,**kwargs)

              return call_func

       return set_func    

 

@route("/index.py")

def index():

       with open("./templates/index.html") as f:

              content=f.read()

       my_stock_info="info XXX"

       content=re.sub(r"\{%content%\}",my_stock_info,content)

       return content

 

@route("/center.py")

def center():

       with open("./templates/center.html") as f:

              content=f.read()

       my_stock_info="查詢數據庫 info XXX"

       content=re.sub(r"\{%content%\}",my_stock_info,content)

       return content

 

def application(env, start_response):

    start_response('200 OK', [('Content-Type', 'text/html')])

    file_name=env['PATH_INFO']

    func=URL_FUNC_DICT[file_name]

    return func()

    return '<h1>Hello, web!</h1>'

首先將判斷改爲字典取引用,然後使用字典,裝飾器在模塊被導入時就已經執行、填充好字典了

04捕捉異常

請求的url一般會對應某個函數,通過字典or元組可實現

使用帶參裝飾器組裝字典:k(url字符串)-v(函數引用)

通過閉包的數據空間建立字符串到引用的映射

使用了裝飾器,index引用指向它對應的call_func,call_func中調用原函數,字典中也保存着原函數的引用。所以裝飾隻影響手動調用函數,不影響路由

此時如果url爲不存在的py會在字典查找時出現異常,也可以先用in判斷是否在字典中

但用try捕獲異常更好,因爲異常可以傳遞,如果在函數執行時有異常未處理,仍會導致服務器出現問題

def application(env, start_response):

    start_response('200 OK', [('Content-Type', 'text/html')])

    file_name=env['PATH_INFO']

    try:

        return URL_FUNC_DICT[file_name]()

    except Exception as ret:

        return "產生了異常:%s" % str(ret)#保證返回web服務器有body

    return '<h1>Hello, web!</h1>'

如果在捕捉到異常:

'gbk' codec can't decode byte 0xaa in position 225: illegal multibyte sequence

此種錯誤,可能是要處理的字符串本身不是gbk編碼,但是卻以gbk編碼去解碼 。比如,字符串本身是utf-8的,但是卻用gbk去解碼utf-8的字符串,所以結果不用說,則必然出錯。可以按照如下的步驟進行嘗試:

(1)在打開文本時候,可以指明打開方式:

file = open(path, encoding='gbk')或file = open(path, encoding='utf-8')

(2)如果上一步還不能解決,可能是文本中出現的一些特殊符號超出了gbk的編碼範圍,可以選擇編碼範圍更廣的‘gb18030’,如:

 file = open(path, encoding='gb18030')

(3)如果上一步還不能解決,說明文中出現了連‘gb18030’也無法編碼的字符,可以使用‘ignore’屬性忽略非法字符,如:file = open(path, encoding='gb18030', errors='ignore')或者file=open(path).read().decode(‘gb18030’,’ignore’)

05路由概念

路由器:數據轉發,IP指明道路。

有兩個以上的網卡,一個用於接收數據放到內存,一個將內存中數據發走

來一個請求,調用對應的函數爲它服務,使用映射實現

06靜態、動態、僞靜態url

url資源定位符中,真靜態url有真實的物理路徑:域名/news/1.html

優點:不用經過框架比較快,對關鍵字SEO搜索引擎優化較好-權重高 排名靠前

缺點:不便於優化修改

動態url(常根據參數給不同參數): 域名/news/1.asp?id=5,常見後綴有jsp、asp、php、py

僞靜態url:域名/course/1.html形式,但也是邏輯地址,沒有物理地址

其中course作爲變量名、1作爲值

需求:目前網頁是.py結尾,實現僞靜態支持

07支持僞靜態

瀏覽器請求->web server->後綴.py的->frame

                     ↓

                     直接獲取

僞靜態的支持應該在服務器修改,如果服務器只給frame轉發.py的請求,那麼框架沒有機會處理僞靜態

把html後綴結尾也作爲動態處理,靜態剩下css/js/png等

同時裝飾器中的路由、頁面中py結尾的href也改成html

08 支持mysql

創建數據庫stock_db,導入表(如果在Linux環境,則在當前文件夾能看見sql文件的地方source命令運行sql文件)需要的sql文件存放在鏈接的壓縮包中

股票信息頁面顯示info表數據,個人中心頁面顯示自己關注的股票信息

在框架中修改,原本content的空缺直接用固定字符串填充,現從數據庫查出

Python操作數據庫時默認開啓事務(特性:原子性、一致性、持久性、隔離性),其中查詢不需要commit提交,增刪改需要提交

操作流程爲:導入、鏈接數據庫、獲取遊標,execute執行sql語句

因爲默認環境要安裝該模塊,我使用了虛擬環境

from pymysql import connect

鏈接時記得改database等值

#創建鏈接

    conn = pymysql.connect(host='localhost',port=3306,user='root',password='123456',database='stock_db',charset='utf8')

    #獲得cursor對象

    cursor = conn.cursor()

    #執行sql語句

    sql = """select * from info;"""

    cursor.execute(sql)

    # 存儲查詢出來的數據

    data_from_mysql = cursor.fetchall()  #每一行是一個元組,多行則外部再套一個元組

    #全部關閉

    cursor.close()

    conn.close()

    #不能直接拿元組替換,需要轉成str

    content=re.sub(r"\{%content%\}",str(strdata_from_mysql),content)

此時頁面展示的數據還是一坨,拿一個前端模板套tr、td

向模板填充數據庫中一一對應的各值,此處要注意確認使用的模板html中確實有{%content%}

# 3. 將mysql查詢出來的數據替換到模板中

    line_html = """

                <tr>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>

                        <input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="000007">

                    </td>

                </tr>

    """

    #創建一個空字符串

    code_html = ""

    #遍歷取出的數據,每次放一行模板

    for line_info in data_from_mysql:

        #向模板填充

        code_html+=line_html % (line_info[0],line_info[1],line_info[2],line_info[3],line_info[4],line_info[5],line_info[6],line_info[7])

 

    content=re.sub(r"\{%content%\}",code_html,content)

最後添加還需要一個值,不過先留着

09個人中心頁面數據

頁面中能點的能看的都由前端負責,後端只需要提供它的數據服務

修改模板

關聯兩表查詢,修改sql

根據focus表中的info_id在info表查找

select * from info as i inner join focus as f on i.id=f.info_id;

查找結果:

+----+--------+----------+---------+----------+-------+-------+------------+----+------------------+---------+

| id | code   | short    | chg     | turnover | price | highs | time       | id | note_info        | info_id |

+----+--------+----------+---------+----------+-------+-------+------------+----+------------------+---------+

| 36 | 300268 | 萬福生科 | -10.00% | 0.27%    | 31.77 | 13.57 | 2017-04-10 |  2 | 你確定要買這個? |      36 |

| 37 | 300280 | 南通鍛壓 | 3.31%   | 0.66%    | 32.2  | 32    | 2017-04-11 |  3 | 利好             |      37 |

| 88 | 601678 | 濱化股份 | 0.13%   | 2.47%    | 7.92  | 7.91  | 2017-07-20 |  9 |                  |      88 |

| 89 | 601918 | 新集能源 | 1.23%   | 3.11%    | 4.93  | 4.92  | 2017-07-19 | 10 |                  |      89 |

|  1 | 000007 | 全新好   | 10.01%  | 4.40%    | 16.05 | 14.6  | 2017-07-18 | 13 |                  |       1 |

+----+--------+----------+---------+----------+-------+-------+------------+----+------------------+---------+

Info中不需要的數據有序號id、時間,focus中需要備註信息

select i.code,i.short,i.chg,i.turnover,i.price,i.highs,f.note_info

 from info as i inner join focus as f on i.id=f.info_id;

Center函數

@route("/center.py")

def center():

    with open("./templates/center.html", encoding='UTF-8') as f:

        content = f.read()

    conn = pymysql.connect(host='localhost',port=3306,user='root',password='123456',database='stock_db',charset='utf8')

    cursor = conn.cursor()

    #修改sql語句

    sql = """select i.code,i.short,i.chg,i.turnover,i.price,i.highs,f.note_info from info as i inner join focus as f on i.id=f.info_id;

"""

    cursor.execute(sql)

    data_from_mysql = cursor.fetchall() 

    cursor.close()

    conn.close()

    #修改模板

    line_html = """

                <tr>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>%s</td>

                    <td>

                        <a type="button" class="btn btn-default btn-xs" href=""><span class="glyphicon glyphicon-star" aria-hidden="true">→</span>修改</a>

                    </td>

                    <td>

                        <input type="button" value="刪除" id="toDel" name="toDel" systemidvaule="000007">

                    </td>

                </tr>

    """

    code_html = ""

    for line_info in data_from_mysql:

        #修改向模板填充的數據個數

        code_html+=line_html % (line_info[0],line_info[1],line_info[2],line_info[3],line_info[4],line_info[5],line_info[6])

 

    content=re.sub(r"\{%content%\}",code_html,content)   

    # html_content = re.sub(r"\{%content%\}", data_from_mysql, html_content)

    return content

新增請求流程:確定需要哪些url,編寫對它處理的函數,使用裝飾器

而web框架核心基本功能不變:application、route裝飾器及其字典

面向切面(新功能只需要定義一個函數讓它知道,就像往其中插一個切面,只需要注意這一個面的功能,不需要關注整體)用裝飾器加一個功能,用原本的框架調用

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