Table of Contents
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) |
缺點:
- 裝飾器一般不能影響原函數調用,定義函數時不用參數,調用時就不應該需要參數。而這個參數是給裝飾器傳的,不是給原函數傳的。如果函數多次調用,則每次都需要給函數傳對應裝飾器需要的參數。
- 裝飾器需要驗證的級別應該是在裝飾函數時規定好的,如果在調用函數時自己傳該參數,可自定義驗證級別,不能有效驗證
帶參數裝飾器:
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裝飾器及其字典
面向切面(新功能只需要定義一個函數讓它知道,就像往其中插一個切面,只需要注意這一個面的功能,不需要關注整體)用裝飾器加一個功能,用原本的框架調用