版權(quán)聲明:本文為CSDN博主「一笑照夜」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/erwugumo/article/details/96146119
1、為什么需要異常處理
先看一下我們?cè)诘谑轮髮懲甑拇a:
from flask import Flask, render_template,request,redirect,escape,session
from vsearch import search4letters
from DBcm import UseDatabase
from checker import check_logged_in
app=Flask(__name__)
app.secret_key='YouWillNeverGuess'
#無(wú)法連接到數(shù)據(jù)庫(kù)怎么辦?
app.config['dbconfig']={'host':'127.0.0.1',
'user':'vsearch',
'password':'vsearchpasswd',
'database':'vsearchlogDB',}
def log_request(req:'flask_request',res:str)->None:
with UseDatabase(app.config['dbconfig']) as cursor:
#這里能夠防范SQL攻擊嗎?
_INSERT="""insert into log
(phrase,letters,ip,browser_string,results)
values
(%s,%s,%s,%s,%s)"""
#在執(zhí)行SQL代碼的時(shí)候卡死怎么辦?
cursor.execute(_INSERT,(req.form['phrase'],
req.form['letters'],
req.remote_addr,
req.user_agent.browser,
res,))
@app.route('/search4',methods=['POST'])
def do_search() -> 'html':
phrase=request.form['phrase']
letters=request.form['letters']
results=str(search4letters(phrase,letters))
#函數(shù)調(diào)用失敗怎么辦?
log_request(request,results)
return render_template('results.html',
the_title='Here are your results',
the_phrase=phrase,
the_letters=letters,
the_results=results)
@app.route('/')
@app.route('/entry')
def entry_page() -> 'html':
return render_template('entry.html',
the_title='Welcome to search4letters on the web!')
@app.route('/viewlog')
@check_logged_in
def view_the_log()->str:
with UseDatabase(app.config['dbconfig']) as cursor:
#這里能夠防范SQL攻擊嗎?
_SELECT="""select phrase,letters,ip,browser_string,results from log"""
cursor.execute(_SELECT)
#在執(zhí)行SQL代碼的時(shí)候卡死怎么辦?
contents=cursor.fetchall()
titles=('Phrase','Letters','Remote_addr','User_agent','Results')
return render_template('viewlog.html',
the_title='View Log',
the_row_titles=titles,
the_data=contents,)
@app.route('/login')
def do_login()->str:
session['logged_in']=True
return 'You are now logged in'
@app.route('/logout')
def do_logout()->str:
session.pop('logged_in')
return 'You are now logged out.'
@app.route('/status')
def check_status()->str:
if 'logged_in' in session:
return 'You are currently logged in.'
return 'You are NOT logged in.'
if __name__=='__main__':
from werkzeug.contrib.fixers import ProxyFix
app.wsgi_app=ProxyFix(app.wsgi_app)
app.run()
在注釋中我提出了以下幾個(gè)問(wèn)題:
無(wú)法連接到SQL數(shù)據(jù)庫(kù)怎么辦?遭受SQL注入攻擊怎么辦?處理時(shí)間過(guò)長(zhǎng)怎么辦?函數(shù)調(diào)用出錯(cuò)怎么辦?
這些都屬于異常,我們應(yīng)對(duì)這些異常的出現(xiàn)事先做好準(zhǔn)備。如何做好準(zhǔn)備呢?一般是給出通知,記錄下錯(cuò)誤的類型,出現(xiàn)時(shí)間等,便于定位錯(cuò)誤甚至復(fù)現(xiàn)。
下面分別分析這四個(gè)異常:
①數(shù)據(jù)庫(kù)連接失?。?/h4>
我們?cè)诤笈_(tái)關(guān)閉SQL服務(wù),就會(huì)出現(xiàn)如下的InterfaceError錯(cuò)誤。

很常見,只要你的代碼依賴的外部資源不可用,就會(huì)出現(xiàn)錯(cuò)誤。出現(xiàn)這種情況時(shí),解釋器會(huì)報(bào)錯(cuò)“InterfaceError”,可以使用python的內(nèi)置異常處理機(jī)制發(fā)現(xiàn)這個(gè)錯(cuò)誤并做出反應(yīng)。
②數(shù)據(jù)庫(kù)受到攻擊
暫時(shí)不用考慮這點(diǎn),python的DB-API已經(jīng)有了對(duì)常見攻擊的防范。他們做的比我們好得多。
③代碼運(yùn)行時(shí)間過(guò)長(zhǎng)
這其實(shí)不是異常,只是代碼優(yōu)化問(wèn)題或者單純因?yàn)榉?wù)器太差了。但是用戶可能認(rèn)為是網(wǎng)站已經(jīng)崩潰,為了讓用戶知道網(wǎng)站不是崩潰而是在很努力的處理用戶的請(qǐng)求,我們需要一些措施。
④函數(shù)調(diào)用出錯(cuò)
這是我自己的問(wèn)題了,太菜導(dǎo)致代碼本身有問(wèn)題,解釋器會(huì)給出錯(cuò)誤提示,我們只需要記錄這個(gè)錯(cuò)誤即可,本質(zhì)上與第一個(gè)問(wèn)題是相同的。
比如說(shuō)常見的RuntimeError錯(cuò)誤等。
2、開始異常處理——保護(hù)log_request函數(shù)
由于問(wèn)題①和問(wèn)題④的特點(diǎn)類似,我們就先從這倆入手。
python實(shí)際上是有一組非常豐富的內(nèi)置異常類型的,它涵蓋了許多我們使用python時(shí)可能出現(xiàn)的錯(cuò)誤。我們看到的這些所有異常都屬于一個(gè)名為exception的類,這些異常按層次結(jié)構(gòu)組織。
如果一個(gè)錯(cuò)誤不存在于內(nèi)置異常該怎么辦?這就需要我們定制異常。第一種錯(cuò)誤報(bào)的錯(cuò)“InterfaceError”就是mysql.connector的一個(gè)定制異常。
怎么發(fā)現(xiàn)一個(gè)異常呢?需要使用Python的try語(yǔ)句,學(xué)過(guò)java應(yīng)該會(huì)好理解一些,Java中也有類似的try-catch語(yǔ)句。運(yùn)行時(shí)如果出現(xiàn)問(wèn)題,try可以幫助你處理異常。來(lái)看下面幾行有問(wèn)題的代碼:
with open('myfile.txt') as fh:
file_data=fh.read()
print(file_data)
看上去好像沒(méi)什么問(wèn)題,然而,如果你所在的用戶組沒(méi)有讀取權(quán)限,或者myfile這個(gè)文件現(xiàn)在還不存在,那么就會(huì)產(chǎn)生錯(cuò)誤。運(yùn)行一下試試看。

嗯,報(bào)錯(cuò)FileNotFoundError。python很懂啊,看來(lái)這是個(gè)常見的異常,總會(huì)有人蠢蠢的在建文件之前就去讀取文件,以至于python的內(nèi)置異常中有了這么一條。
出現(xiàn)運(yùn)行時(shí)錯(cuò)誤時(shí),就會(huì)產(chǎn)生一個(gè)異常,如果我們忽略這個(gè)異常,就稱為這個(gè)異常未捕獲,解釋器就會(huì)強(qiáng)行終止我們的代碼,然后顯示一個(gè)運(yùn)行時(shí)錯(cuò)誤消息,就是上面紅的四行。當(dāng)然我們可以選擇用try來(lái)捕獲這個(gè)異常,但是只捕獲還不夠,還得去進(jìn)一步說(shuō)明該養(yǎng)啊該殺啊燉了吃肉還是燒烤什么的。因此在用try捕獲之后還要寫代碼來(lái)描述之后干嘛,不然捕獲和未捕獲沒(méi)什么兩樣。
在捕獲之后,可以選擇:忽略異常(那你捕獲它干啥),運(yùn)行另外一些代碼來(lái)代替出錯(cuò)的代碼,記錄出現(xiàn)的異常等,無(wú)論選擇哪種處理方式,都要使用try。
為了用try保護(hù)代碼,就要把代碼放在try的代碼組中。如果產(chǎn)生了一個(gè)異常,try代碼組中的代碼會(huì)終止,然后運(yùn)行except中的代碼,在這個(gè)except的代碼組中定義如何處理。如下:
try:
with open('myfile.txt') as fh:
file_data=fh.read()
print(file_data)
except FileNotFoundError:
print('The data file is missing.')
這時(shí)再運(yùn)行上面代碼,發(fā)現(xiàn)錯(cuò)誤信息發(fā)生了改變:

說(shuō)明try的確捕獲到了這個(gè)異常,并返回了通知。
那我們新建myfile文件,并設(shè)置為只讀。對(duì)其執(zhí)行寫操作,如下:
try:
with open('myfile.txt','w') as fh:
file_data=fh.read()
print(file_data)
except FileNotFoundError:
print('The data file is missing.')
會(huì)報(bào)錯(cuò)PermisssionError,如下:

這次try沒(méi)能捕獲這個(gè)異常,因?yàn)檫@個(gè)異常在except中沒(méi)有對(duì)應(yīng)的處理方式,既然知道了原因,增加這種異常的處理即可:
try:
with open('myfile.txt','w') as fh:
file_data=fh.read()
print(file_data)
except FileNotFoundError:
print('The data file is missing.')
except PermissionError:
print('This is not allowed.')
再次運(yùn)行如下:

現(xiàn)在問(wèn)題來(lái)了,我不可能預(yù)見到所有的異常,一旦出現(xiàn)未預(yù)見到的異常,就會(huì)導(dǎo)致未捕獲,這對(duì)于用戶的使用體驗(yàn)影響很大。因此我們還是需要一個(gè)能夠捕獲所有異常的異常處理器,但是只對(duì)那些常見的異常有對(duì)應(yīng)的通知,對(duì)于不常見的異常,我們均返回相同的通知即可。只需要在最后加兩行代碼即可:
try:
with open('myfile.txt','w') as fh:
file_data=fh.read()
print(file_data)
except FileNotFoundError:
print('The data file is missing.')
except PermissionError:
print('This is not allowed.')
except:
print('Some other error occured.')
這就類似C中的switch-case一樣,不過(guò)switch的參數(shù)是我們輸入的,然后去case找對(duì)應(yīng);而try-except則是解釋器給參數(shù),也在except中找對(duì)應(yīng)。
但是捕獲所有異常這種方法有一個(gè)缺點(diǎn):除了FileNotFoundError和PermisssionError以外,我們不知道出了什么錯(cuò)誤,因?yàn)檫@兩種錯(cuò)誤是特殊的,我們可以立刻反應(yīng)過(guò)來(lái),其他的通知都是一樣的,所以沒(méi)法知道。那該怎么辦呢?try可以知道發(fā)生了什么錯(cuò)誤,然后在except中對(duì)應(yīng),能不能讓try先記錄下來(lái),然后再對(duì)應(yīng)呢?
可以的。有兩種方法:使用sys模塊的功能,使用擴(kuò)展的try/except技術(shù)。
sys模塊可以用于訪問(wèn)解釋器的內(nèi)部信息,其中有一個(gè)函數(shù)exc_info,它會(huì)提供當(dāng)前處理的異常的有關(guān)信息。調(diào)用該函數(shù)時(shí),它會(huì)返回一個(gè)包括三個(gè)值的元組,第一個(gè)值是異常的類型,第二個(gè)字詳細(xì)描述異常的值,第三個(gè)值包含一個(gè)回溯跟蹤對(duì)象,通過(guò)該對(duì)象可以訪問(wèn)回溯跟蹤消息。如果當(dāng)前沒(méi)有異常,則會(huì)返回三個(gè)None。
舉例如下:

首先必須import sys模塊,不然會(huì)報(bào)錯(cuò)。
然后在try中寫下一個(gè)會(huì)報(bào)異常的代碼,這里是除零異常。
最后在except中調(diào)用exc_info函數(shù),并打印該元組。
元組元素第一個(gè)是異常的類型,可以看出是ZeroDivisionError,即除零錯(cuò)誤類;第二個(gè)是異常的值;第三個(gè)是對(duì)象。
雖然我們通過(guò)查詢回溯跟蹤對(duì)象可以更深入的了解,但是現(xiàn)在只需要知道異常類型就足夠用了,也即是說(shuō),現(xiàn)在只需要元組的第一個(gè)元素。
也就是說(shuō)我們只需要儲(chǔ)存err[0]就可以咯。實(shí)際上更簡(jiǎn)單,由于這種方式十分常用,python擴(kuò)展了try-except的功能,讓它直接支持這種方法查看異常,也不用import sys模塊,也不用自己看,而只需要按如下方式修改代碼:
try:
with open('myfile.txt','w') as fh:
file_data=fh.read()
print(file_data)
except FileNotFoundError:
print('The data file is missing.')
except PermissionError:
print('This is not allowed.')
except Exception as err:
print('Some other error occured:',str(err))
也就是說(shuō),在遇到其他異常,會(huì)把這個(gè)異常對(duì)象賦給一個(gè)變量,一般稱為err,然后就可以輸出這個(gè)變量了。
接下來(lái)進(jìn)入正題:如果我們應(yīng)用中的log_request函數(shù)調(diào)用失敗怎么辦?
當(dāng)然是把這個(gè)函數(shù)寫進(jìn)try的代碼組啊。如下:
@app.route('/search4',methods=['POST'])
def do_search() -> 'html':
phrase=request.form['phrase']
letters=request.form['letters']
results=str(search4letters(phrase,letters))
try:
log_request(request,results)
except Exception as err:
print('******Logging failed with this error:',str(err))
return render_template('results.html',
the_title='Here are your results',
the_phrase=phrase,
the_letters=letters,
the_results=results)
注意不要把return部分寫進(jìn)代碼組。
在修改之后,即使log_request函數(shù)調(diào)用失敗,也不會(huì)阻礙網(wǎng)頁(yè)上顯示結(jié)果,而只會(huì)在日志記錄上失敗。極大提高了用戶的使用體驗(yàn),用戶根本不會(huì)知道你的網(wǎng)頁(yè)有過(guò)問(wèn)題,而這個(gè)報(bào)錯(cuò)信息也會(huì)被隱藏在后臺(tái)。因?yàn)閜rint輸出在后臺(tái),而不是在網(wǎng)頁(yè):

因此,即使代碼出錯(cuò)也不會(huì)導(dǎo)致整個(gè)應(yīng)用的崩潰,提高了應(yīng)用的魯棒性。
3、進(jìn)階——保護(hù)view_the_log函數(shù)
上文我們保護(hù)了log_request函數(shù),很簡(jiǎn)單,只需要把該函數(shù)的調(diào)用部分卸載try的代碼組里就可以。接下來(lái)看view_the_log函數(shù)。
@app.route('/viewlog')
@check_logged_in
def view_the_log()->str:
#contents=[]
with UseDatabase(app.config['dbconfig']) as cursor:
_SELECT="""select phrase,letters,ip,browser_string,results from log"""
cursor.execute(_SELECT)
contents=cursor.fetchall()
titles=('Phrase','Letters','Remote_addr','User_agent','Results')
return render_template('viewlog.html',
the_title='View Log',
the_row_titles=titles,
the_data=contents,)
這個(gè)函數(shù)用于查看日志,它并不是我們自己調(diào)用的,也就是說(shuō)我們沒(méi)法寫一個(gè)try把它放在里面。因?yàn)樗c一個(gè)url直接相連,真正調(diào)用它的地方在flask內(nèi)部。那該怎么保護(hù)它呢?
如果沒(méi)法保護(hù)它的調(diào)用,至少要保護(hù)它的代碼。就是這樣。
代碼會(huì)出哪些問(wèn)題?比如說(shuō)后端數(shù)據(jù)庫(kù)不可用,比如說(shuō)可能無(wú)法登陸,比如說(shuō)查詢失敗等等等等。
我們當(dāng)然可以把函數(shù)的代碼全放在try的代碼組中,在return下面再寫一個(gè)except,但是這樣做不太好。比如說(shuō)如果我想針對(duì)數(shù)據(jù)庫(kù)不可用這一異常做出特定的反映,這種捕獲所有異常的方法顯然無(wú)法實(shí)現(xiàn)這個(gè)功能。
那好辦,為這個(gè)異常定制一個(gè)返回不就可以了。
當(dāng)然,像下面這樣:
@app.route('/viewlog')
@check_logged_in
def view_the_log()->str:
#contents=[]
try:
with UseDatabase(app.config['dbconfig']) as cursor:
_SELECT="""select phrase,letters,ip,browser_string,results from log"""
cursor.execute(_SELECT)
contents=cursor.fetchall()
titles=('Phrase','Letters','Remote_addr','User_agent','Results')
return render_template('viewlog.html',
the_title='View Log',
the_row_titles=titles,
the_data=contents,)
except mysql.connector.errors.InterfaceError as err:
print('Is your database switched on? Error:',str(err))
except Exception as err:
print('Something went srong:',str(err))
注意,這里定制異常的時(shí)候,不能直接寫InterfaceError,因?yàn)檫@個(gè)異常的定義在connector中,而不像之前文件權(quán)限異常等是默認(rèn)異常。因此需要import mysql.connector模塊來(lái)識(shí)別出這個(gè)異常。
現(xiàn)在就能夠給出特定的異常通知了。
但是這并不好。為什么?我們更改的代碼和mysql這個(gè)數(shù)據(jù)庫(kù)耦合的太緊了。這可能有點(diǎn)難理解。通俗一點(diǎn)來(lái)說(shuō)就是我們現(xiàn)在的代碼和mysql糾纏太深,如果我們想更換別的數(shù)據(jù)庫(kù),需要改的地方太多了,不能很快的改過(guò)去。這對(duì)于主程序來(lái)說(shuō)是一個(gè)很大的缺點(diǎn)。
如何改進(jìn)呢?
在DBcm接口中使用緊耦合的代碼,并提供一個(gè)接口,主程序通過(guò)這個(gè)接口就能實(shí)現(xiàn)和import mysql.connector一樣的功能,若是想更改數(shù)據(jù)庫(kù),根本不用改主程序,因?yàn)樗褂玫氖荄Bcm的接口,只需要改DBcm的代碼就可以了。這樣就實(shí)現(xiàn)了主程序和數(shù)據(jù)庫(kù)的解耦。
之前我們?cè)趯慏Bcm的代碼時(shí),目的是寫一個(gè)上下文管理器,它的exit函數(shù)有四個(gè)參數(shù),后三個(gè)參數(shù)就是用來(lái)做這個(gè)的?,F(xiàn)在終于可以用上了:exc_type、exc_value、exc_trace,正好對(duì)應(yīng)元組中的三個(gè)元素。我們來(lái)看原來(lái)的DBcm代碼:
import mysql.connector
class UseDatabase:
def __init__(self,dbconfig:dict)->None:
self.dbconfig=dbconfig
def __enter__(self)->'cursor':
self.conn=mysql.connector.connect(**self.dbconfig)
self.cursor=self.conn.cursor()
return self.cursor
def __exit__(self,exc_type,exc_value,exc_trace)->None:
self.conn.commit()
self.cursor.close()
self.conn.close()
如果出問(wèn)題,會(huì)有什么后果呢?
如果enter出問(wèn)題,那with會(huì)直接終止,后續(xù)的exit處理也會(huì)取消。因?yàn)?strong>enter都出問(wèn)題了,上下文正確配置好的概率微乎其微,連接可能還沒(méi)建立,你斷開個(gè)毛線。
那enter會(huì)出什么問(wèn)題呢?最大的問(wèn)題應(yīng)該是后端數(shù)據(jù)庫(kù)不可用,連接建立失敗,要針對(duì)這個(gè)生成一個(gè)定制異常。
如何創(chuàng)建一個(gè)定制異常?
首先重申一下定制異常是什么。定制異常是python的Exception類中沒(méi)有的異常,因?yàn)闆](méi)有這種異常,因此需要我們自己寫,也就是定制。一般是針對(duì)一種情況,給他起個(gè)別名,這就是定制異常。InterfaceError就是一個(gè)定制異常。然而為了脫耦,我們要把這個(gè)定制異常寫成我們自己的定制異常,從而讓主程序捕獲我們的異常,當(dāng)數(shù)據(jù)庫(kù)改變時(shí),就直接改我們的定制異常就可以,主程序捕獲的不變,這就是脫耦的原理。
定制異常也是異常,因此它要繼承Exception這個(gè)類。下面做一個(gè)簡(jiǎn)單的實(shí)驗(yàn):
class ConnectionError(Exception):
pass
我們定義了一個(gè)名為ConnectionError的異常,它繼承了Exception的類,繼承某個(gè)類A只需要定義的時(shí)候在類名后加上(A)即可。
這是一個(gè)空類,但并不代表它什么都做不了,至少它具有Exception類的所有功能,因此看上去就好像是在Exception類中新加了一個(gè)成員一樣。
如何引發(fā)這個(gè)異常呢?他是個(gè)空類,也沒(méi)告訴我什么時(shí)候可能會(huì)出現(xiàn)這個(gè)異常啊。
使用raise產(chǎn)生這個(gè)異常。如下:

會(huì)產(chǎn)生一個(gè)回溯跟蹤消息,表明產(chǎn)生了一個(gè)異常。
也可以使用try-except來(lái)捕獲這個(gè)異常,如下:

可以看到,try成功捕獲了這個(gè)異常。
還可以看到這一點(diǎn):即用raise產(chǎn)生一個(gè)異常時(shí),異常名后面括號(hào)里的字符串實(shí)際上就是異常的類型,可以調(diào)整這里更改err輸出的內(nèi)容。
接下來(lái)我們要修改DBcm的代碼,定制一個(gè)自己的異常,用于反映數(shù)據(jù)庫(kù)連接失敗。如下:
import mysql.connector
class ConnectionError(Exception):
pass
class UseDatabase:
def __init__(self,dbconfig:dict)->None:
self.dbconfig=dbconfig
def __enter__(self)->'cursor':
try:
self.conn=mysql.connector.connect(**self.dbconfig)
self.cursor=self.conn.cursor()
return self.cursor
except mysql.connector.errors.InterfaceError as err:
raise ConnectionError(err)
def __exit__(self,exc_type,exc_value,exc_trace)->None:
self.conn.commit()
self.cursor.close()
self.conn.close()
套路是一樣的,首先定義一個(gè)新類,然后當(dāng)enter運(yùn)行時(shí),把內(nèi)部代碼放在try的代碼組內(nèi),注意三句都要放進(jìn)去,而不是只放建立連接那句。因?yàn)橹环拍且痪涞脑?,如果連接建立失敗,雖然會(huì)捕獲異常,但是剩下兩句還是會(huì)繼續(xù)運(yùn)行,還是會(huì)報(bào)錯(cuò)導(dǎo)致應(yīng)用崩潰。如果三句都放進(jìn)去,第一句出錯(cuò)的話,后面兩句直接不會(huì)運(yùn)行。
然后用except捕獲因無(wú)法連接數(shù)據(jù)庫(kù)而產(chǎn)生的異常mysql.connector.errors.InterfaceError,將它的類型儲(chǔ)存在err中,然后產(chǎn)生我們自己的異常ConnectionError,我們的異常的類型就是err。
簡(jiǎn)而言之,這是一個(gè)接力:連接數(shù)據(jù)庫(kù)出錯(cuò)→mysql.connector產(chǎn)生異常InterfaceError→捕獲該異常→產(chǎn)生異常ConnectionError。
接下來(lái)修改view_the_log函數(shù)如下:
from DBcm import UseDatabase,ConnectionError
@app.route('/viewlog')
@check_logged_in
def view_the_log()->str:
#contents=[]
try:
with UseDatabase(app.config['dbconfig']) as cursor:
_SELECT="""select phrase,letters,ip,browser_string,results from log"""
cursor.execute(_SELECT)
contents=cursor.fetchall()
titles=('Phrase','Letters','Remote_addr','User_agent','Results')
return render_template('viewlog.html',
the_title='View Log',
the_row_titles=titles,
the_data=contents,)
except ConnectionError as err:
print('Is your database switched on? Error:',str(err))
except Exception as err:
print('Something went wrong:',str(err))
return 'Error'
注意import 我們的異常類。
在最后return一個(gè)字符串。
另外,由于我不知道如何出現(xiàn)“無(wú)法找到后端數(shù)據(jù)庫(kù)”的錯(cuò)誤,因此我選擇關(guān)閉mysql服務(wù),它的異常名為mysql.connector.errors.DatabaseError,針對(duì)這個(gè)異常,只需要在DBcm中加入多一行except,如下:
import mysql.connector
class ConnectionError(Exception):
pass
class UseDatabase:
def __init__(self,dbconfig:dict)->None:
self.dbconfig=dbconfig
def __enter__(self)->'cursor':
try:
self.conn=mysql.connector.connect(**self.dbconfig)
self.cursor=self.conn.cursor()
return self.cursor
except mysql.connector.errors.InterfaceError as err:
raise ConnectionError(err)
except mysql.connector.errors.DatabaseError as err:
raise ConnectionError(err)
def __exit__(self,exc_type,exc_value,exc_trace)->None:
self.conn.commit()
self.cursor.close()
self.conn.close()
十分方便。效果如下:
接下來(lái)考慮下一個(gè)問(wèn)題:
enter函數(shù)中出現(xiàn)異常,我們?cè)诤瘮?shù)內(nèi)部捕獲,那try代碼組出現(xiàn)異常怎么辦?總不能到代碼組去捕獲吧?
為什么不能呢?
因?yàn)槲覀冞@個(gè)代碼組是運(yùn)行SQL代碼的,如果在代碼組捕獲,又需要import SQL了,再次緊耦合,因此不能在代碼組捕獲。這時(shí)候exit的后三個(gè)參數(shù)就派上用場(chǎng)了:若是try的代碼組出現(xiàn)異常,會(huì)將這一異常的三元素傳入exit的后三個(gè)參數(shù)中,在exit中可以對(duì)代碼組中的異常進(jìn)行處理。
接下來(lái)擴(kuò)展兩個(gè)定制異常:
CredentialsError:當(dāng)enter方法中出現(xiàn)ProgrammingError錯(cuò)誤時(shí)產(chǎn)生這個(gè)異常。
SQLError:當(dāng)exit方法中出現(xiàn)ProgrammingError錯(cuò)誤時(shí)產(chǎn)生這個(gè)異常。
ProgrammingError異常一般出現(xiàn)在訪問(wèn)數(shù)據(jù)庫(kù)的憑據(jù)錯(cuò)誤(字典中的密碼錯(cuò)了什么的)或者是SQL語(yǔ)句出現(xiàn)語(yǔ)法錯(cuò)誤時(shí)出現(xiàn)。這也是為什么enter函數(shù)中出現(xiàn)這個(gè)異常叫CredentialsError,因?yàn)?strong>enter函數(shù)需要用到憑據(jù);而exit函數(shù)會(huì)接收try代碼組中的錯(cuò)誤,代碼組中有SQL語(yǔ)句。
第一個(gè)定制異常很簡(jiǎn)單,原理和之前的一樣,因此不用強(qiáng)調(diào)。然而第二個(gè)定制異常有一些問(wèn)題。
第二個(gè)定制異常與代碼組中的異常有關(guān),代碼組中的異常類型將傳入exc_type,因此要在exit中判斷exc_type是否是ProgrammingError。在哪里判斷呢?一定要在exit的最后判斷,也就是exit把自己當(dāng)工作都做完了再判斷。因?yàn)槿羰桥袛喑晒?,則會(huì)引起一個(gè)異常,那其余代碼就不會(huì)繼續(xù)運(yùn)行,對(duì)于我們的代碼,連接就不會(huì)斷開了,這是不可取的。另外,如果出現(xiàn)其他異常,可以在判斷完P(guān)rogrammingError之后再進(jìn)行其他判斷。代碼如下:
import mysql.connector
class ConnectionError(Exception):
pass
class CredentialsError(Exception):
pass
class SQLError(Exception):
pass
class UseDatabase:
def __init__(self,dbconfig:dict)->None:
self.dbconfig=dbconfig
def __enter__(self)->'cursor':
try:
self.conn=mysql.connector.connect(**self.dbconfig)
self.cursor=self.conn.cursor()
return self.cursor
except mysql.connector.errors.InterfaceError as err:
raise ConnectionError(err)
except mysql.connector.errors.DatabaseError as err:
raise ConnectionError(err)
except mysql.connector.errors.ProgrammingError as err:
raise CredentialsError(err)
def __exit__(self,exc_type,exc_value,exc_trace)->None:
self.conn.commit()
self.cursor.close()
self.conn.close()
if exc_type is mysql.connector.errors.ProgrammingError:
raise SQLError(exc_value)
elif exc_type:
raise exc_type(exc_value)
@app.route('/viewlog')
@check_logged_in
def view_the_log()->str:
#contents=[]
try:
with UseDatabase(app.config['dbconfig']) as cursor:
_SELECT="""select phrase,letters,ip,browser_string,results from log"""
cursor.execute(_SELECT)
contents=cursor.fetchall()
titles=('Phrase','Letters','Remote_addr','User_agent','Results')
return render_template('viewlog.html',
the_title='View Log',
the_row_titles=titles,
the_data=contents,)
except ConnectionError as err:
print('Is your database or your mysql service switched on? Error:',str(err))
except CredentialsError as err:
print('User-id/Password issues.Error:',str(err))
except SQLError as err:
print('Is your query correct?Error:',str(err))
except Exception as err:
print('Something went wrong:',str(err))
return 'Error'
在這里,if的判斷使用了is,而我自己改成==也能夠正常運(yùn)行,is和==的區(qū)別參考該網(wǎng)址。
效果如下:
密碼錯(cuò)誤時(shí):

SQL錯(cuò)誤時(shí):

現(xiàn)在,只剩下那些“需要長(zhǎng)時(shí)間等待”的問(wèn)題等待我們處理了。
這留到下一章。