版權(quán)聲明:本文為作者原創(chuàng)文章钱豁,可以隨意轉(zhuǎn)載,但必須在明確位置標(biāo)明出處7柚I摺!
上一篇文章我們介紹了函數(shù)的使用幌蚊,以及函數(shù)設(shè)計(jì)應(yīng)該參考的原則谤碳,但這些還不夠的,函數(shù)只是讓我們的代碼可讀性、可復(fù)用性提高了。并沒有讓我們的程序看上去那么健壯颗味,專業(yè)術(shù)語叫「穩(wěn)定行」飘言;什么是穩(wěn)定性呢,就是我們的程序經(jīng)不起考驗(yàn)旦事,就像大海中的孤舟經(jīng)不起一點(diǎn)風(fēng)浪的拍打芭碍,我們上一篇文章的代碼只適合在風(fēng)平浪靜的時(shí)候出海董瞻,因?yàn)槌绦虿]有一點(diǎn)容錯(cuò)處理卷仑,下面該是修煉內(nèi)功的時(shí)候了峻村,不能只有招式?jīng)]有內(nèi)涵啊...,那樣的話你講永遠(yuǎn)成為不了高手锡凝。
內(nèi)功修煉總綱--異常處理
什么是異常處理粘昨,異常處理就是處理程序運(yùn)行期間出現(xiàn)的任何意外或異常情況,程序員的日常生活中窜锯,錯(cuò)誤幾乎是每天都會發(fā)現(xiàn)张肾,有些錯(cuò)誤是致命的,會直接導(dǎo)致程序終止锚扎,直到錯(cuò)誤被修復(fù)后再次執(zhí)行程序吞瞪,但是有些錯(cuò)誤是可以忽略的,所有我們需要有一個(gè)機(jī)制能在程序運(yùn)行中捕獲到異常信息工秩,這樣可以幫助我們更快的解決問題尸饺。
什么是錯(cuò)誤
錯(cuò)誤是指語法或是邏輯上的,語法錯(cuò)誤是值不能被解釋器解釋或不能被編譯器編譯助币,這些錯(cuò)誤必須在程序執(zhí)行前糾正浪听。
當(dāng)語法上沒有什么錯(cuò)后,就剩下邏輯上的錯(cuò)誤了眉菱,邏輯錯(cuò)誤可能是由用戶不完整或不合法的輸入所致迹栓。
什么是異常
程序出現(xiàn)了錯(cuò)誤而在正常流程以外采取的行為,這個(gè)行為分為兩個(gè)階段俭缓,首先是引起異常發(fā)生的錯(cuò)誤克伊,其次是采取可能措施的階段。
第一個(gè)階段是發(fā)生在一個(gè)異常條件后發(fā)生的华坦,只要檢測到并意識到異常條件愿吹,解釋器就會觸發(fā)一個(gè)異常。
第二個(gè)階段是異常發(fā)生后惜姐,程序員可以做出各種不同的處理邏輯犁跪,當(dāng)然也也可忽略異常。異常發(fā)生后需要特別注意的是當(dāng)前異常發(fā)生后的邏輯都不會被執(zhí)行了歹袁,如下我們定義了一個(gè)長度為4的list坷衍,而我們試圖去訪問第五個(gè)元素,那么就會產(chǎn)生一個(gè)越界的異常条舔,而hello world將不再會被執(zhí)行到
items = [1,2,3,4]
print(items[5])
print('hello world')
結(jié)果
Traceback (most recent call last):
File "exception.py", line 2, in <module>
print(items[5])
IndexError: list index out of range
檢測和處理異常語法
異撤愣可以通過try語句來檢測,任何在try語句塊里的代碼都將被監(jiān)測孟抗,except用來捕獲異常迁杨,任何在try語句塊里被檢測到的異常都會被except捕獲到钻心,所以對于程序員來說我們需要考慮當(dāng)異常發(fā)生后做一些收尾工作,像釋放資源仑最,重連數(shù)據(jù)庫等等...
常見的異常錯(cuò)誤
錯(cuò)誤類型 | 描述 |
---|---|
AttributeError | 屬性錯(cuò)誤扔役,特性引用和賦值失敗時(shí)會引發(fā)屬性錯(cuò)誤 |
NameError | 試圖訪問的變量名不存在 |
SyntaxError | 語法錯(cuò)誤,代碼形式錯(cuò)誤 |
Exception | 所有異常的基類警医,因?yàn)樗衟ython異常類都是基類Exception的其中一員亿胸,異常都是從基類Exception繼承的,并且都在exceptions模塊中定義预皇。 |
IOError | 一般常見于打開不存在文件時(shí)會引發(fā)IOError錯(cuò)誤侈玄,也可以解理為輸出輸入錯(cuò)誤 |
KeyError | 使用了映射中不存在的關(guān)鍵字(鍵)時(shí)引發(fā)的關(guān)鍵字錯(cuò)誤 |
IndexError | 索引錯(cuò)誤,使用的索引不存在吟温,常索引超出序列范圍序仙,什么是索引 |
TypeError | 類型錯(cuò)誤,內(nèi)建操作或是函數(shù)應(yīng)于在了錯(cuò)誤類型的對象時(shí)會引發(fā)類型錯(cuò)誤 |
ZeroDivisonError | 除數(shù)為0鲁豪,在用除法操作時(shí)潘悼,第二個(gè)參數(shù)為0時(shí)引發(fā)了該錯(cuò)誤 |
ValueError | 值錯(cuò)誤,傳給對象的參數(shù)類型不正確爬橡,像是給int()函數(shù)傳入了字符串?dāng)?shù)據(jù)類型的參數(shù)治唤。 |
try-except語句
try-except是最常見的異常檢測捕獲寫法,它由try塊和except塊組成糙申,也可以有一個(gè)可選的錯(cuò)誤原因
try:
try_suite # watch for exceptions here 監(jiān)控這里的異常
except Exception[, reason]:
except_suite # exception-handling code 異常處理代碼
try-except-finally語句
在try-except語句中我們可以有多個(gè)except語句塊來捕獲不同的錯(cuò)誤類型宾添,finally語句塊是不管有沒有異常發(fā)生都會被執(zhí)行到,它通常用來最后釋放資源柜裸。
try:
try_suite
except Exception1:
suite_for_Exception1
except (Exception2, Exception3, Exception4):
suite_for_Exceptions_2_3_and_4
except Exception5, Argument5:
suite_for_Exception5_plus_argument
except (Exception6, Exception7), Argument67:
suite_for_Exceptions6_and_7_plus_argument
except:
suite_for_all_other_exceptions
else:
no_exceptions_detected_suite
finally:
always_execute_suite
總綱目錄
同過上面的介紹可能有人要問我寫代碼的時(shí)候不知道可能會發(fā)生什么異常缕陕,有可能一種異常,也有可能幾種異常疙挺,那怎么辦呢扛邑,嗯,不錯(cuò)能想到這個(gè)問題的人證明都是認(rèn)真去思考過的人铐然,下面我們看看異常的繼承關(guān)系蔬崩,這里還沒有講到類的繼承關(guān)系,不過不要緊锦爵,你就想象成你與你爹,你爺爺?shù)年P(guān)系就好奥裸,你爺爺把財(cái)產(chǎn)都留給你了你爹险掀,你爹以后也會把財(cái)產(chǎn)留給你。
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
+-- ModuleNotFoundError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- MemoryError
+-- NameError
| +-- UnboundLocalError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
+-- ReferenceError
+-- RuntimeError
| +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning
可以看到BaseException是異常的祖先湾宙,BaseException下有4兄弟樟氢,分別是SystemExit冈绊、KeyboardInterrupt、GeneratorExit埠啃、Exception異常死宣,所以如何你要捕捉Ctrl + c終端異常那么你的except語句應(yīng)該寫成except KeyboardInterrupt as e;那么從上圖我們可以看到絕大部分的異常都是繼承自Exception的碴开,所以如果我們要捕獲除SystemExit毅该、KeyboardInterrupt、GeneratorExit三種異常外都可以寫成except Exception as e潦牛;
努力做個(gè)像風(fēng)清揚(yáng)一樣的高手
通過上面的介紹眶掌,接下來我們來分析分析我為什么說之前的代碼只適合在風(fēng)平浪靜的時(shí)候出海呢,從我們的代碼可以看出我們的代碼寫出來的邏輯是理想中沒有任何錯(cuò)誤才能得出正確結(jié)果的巴碗,那么如果我們的url地址是個(gè)不可訪問的地址會發(fā)生什么呢朴爬,我們將url地址改為「https://www.qiushibaike1.com」看看會有什么結(jié)果:
from urllib import request
import re
url = 'https://www.qiushibaike1.com'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
headers = {'User-Agent': user_agent}
def read_html(url, headers, codec):
'''[read_html]
[讀取html頁面內(nèi)容]
Arguments:
url {[string]} -- [url地址]
headers {[dict]} -- [用戶代理,這里是一個(gè)字典類型]
codec {[string]} -- [編碼方式]
Returns:
[string] -- [頁面內(nèi)容]
'''
# 構(gòu)建一個(gè)請求對象
req = request.Request(url, headers=headers)
# 打開一個(gè)請求
response = request.urlopen(req)
# 讀取服務(wù)器返回的頁面數(shù)據(jù)內(nèi)容
content = response.read().decode(codec)
return content
def match_element(content, pattern):
'''[match_element]
[匹配元素]
Arguments:
content {[string]} -- [文本內(nèi)容]
pattern {[object]} -- [匹配模式]
Returns:
[list] -- [匹配到的元素]
'''
# 匹配所有用戶信息
userinfos = re.findall(pattern, content)
return userinfos
content = read_html(url, headers, 'utf-8')
pattern = re.compile(r'<div class="article block untagged mb15[\s\S]*?class="stats-vote".*?</div>', re.S)
userinfos = match_element(content, pattern)
if userinfos:
pattern = re.compile(r'<a href="(.*?)".*?<h2>(.*?)</h2>.*?<div class="content">(.*?)</div>', re.S)
for userinfo in userinfos:
item = match_element(userinfo, pattern)
#print(item)
if item:
userid, name, content = item[0]
# 去掉換行符橡淆,<span></span>召噩,<br/>符號
userid = re.sub(r'\n|<span>|</span>|<br/>', '', userid)
name = re.sub(r'\n|<span>|</span>|<br/>', '', name)
content = re.sub(r'\n|<span>|</span>|<br/>', '', content)
print((userid, name, content))
結(jié)果
Traceback (most recent call last):
File "func_scrap.py", line 49, in <module>
content = read_html(url, headers, 'utf-8')
File "func_scrap.py", line 25, in read_html
response = request.urlopen(req)
File "E:\Program Files\Python36\lib\urllib\request.py", line 223, in urlopen
return opener.open(url, data, timeout)
File "E:\Program Files\Python36\lib\urllib\request.py", line 526, in open
response = self._open(req, data)
File "E:\Program Files\Python36\lib\urllib\request.py", line 544, in _open
'_open', req)
File "E:\Program Files\Python36\lib\urllib\request.py", line 504, in _call_chain
result = func(*args)
File "E:\Program Files\Python36\lib\urllib\request.py", line 1361, in https_open
context=self._context, check_hostname=self._check_hostname)
File "E:\Program Files\Python36\lib\urllib\request.py", line 1320, in do_open
raise URLError(err)
urllib.error.URLError: <urlopen error [Errno 11001] getaddrinfo failed>
可以從結(jié)果中看出我們解釋器檢測到了一個(gè)urllib.error.URLError異常,異常信息是urlopen error [Errno 11001] getaddrinfo failed(獲取地址信息失斠菥簟)
為程序增加容錯(cuò)處理機(jī)制具滴,讓程序變得更健壯
修改后的程序代碼如下:
from urllib import request
import re
url = 'https://www.qiushibaike1.com'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
headers = {'User-Agent': user_agent}
def read_html(url, headers, codec):
'''[read_html]
[讀取html頁面內(nèi)容]
Arguments:
url {[string]} -- [url地址]
headers {[dict]} -- [用戶代理,這里是一個(gè)字典類型]
codec {[string]} -- [編碼方式]
Returns:
[string] -- [頁面內(nèi)容]
'''
# 構(gòu)建一個(gè)請求對象
try:
req = request.Request(url, headers=headers)
# 打開一個(gè)請求
response = request.urlopen(req)
# 讀取服務(wù)器返回的頁面數(shù)據(jù)內(nèi)容
content = response.read().decode(codec)
return content
except Exception as e:
print(e)
return None
def match_element(content, pattern):
'''[match_element]
[匹配元素]
Arguments:
content {[string]} -- [文本內(nèi)容]
pattern {[object]} -- [匹配模式]
Returns:
[list] -- [匹配到的元素]
'''
# 匹配所有用戶信息
userinfos = re.findall(pattern, content)
return userinfos
content = read_html(url, headers, 'utf-8')
pattern = re.compile(r'<div class="article block untagged mb15[\s\S]*?class="stats-vote".*?</div>', re.S)
print('執(zhí)行到這里了痊银,表示我還沒有終止抵蚊!')
if content:
userinfos = match_element(content, pattern)
if userinfos:
pattern = re.compile(r'<a href="(.*?)".*?<h2>(.*?)</h2>.*?<div class="content">(.*?)</div>', re.S)
for userinfo in userinfos:
item = match_element(userinfo, pattern)
#print(item)
if item:
userid, name, content = item[0]
# 去掉換行符,<span></span>溯革,<br/>符號
userid = re.sub(r'\n|<span>|</span>|<br/>', '', userid)
name = re.sub(r'\n|<span>|</span>|<br/>', '', name)
content = re.sub(r'\n|<span>|</span>|<br/>', '', content)
print((userid, name, content))
執(zhí)行結(jié)果
<urlopen error [Errno 11001] getaddrinfo failed>
執(zhí)行到這里了贞绳,表示我還沒有終止!
可以看到我們的程序并沒有在拋出異常導(dǎo)致程序終止致稀,如果我們不寫try-except再去執(zhí)行上面的代碼冈闭,程序肯定執(zhí)行不到“執(zhí)行到這里了,表示我還沒有終止抖单!”這里了萎攒。大家可以試試
urllib.error.URLError異常
從上面的途中我們可以看到程序拋出了urllib.error.URLError異常,所以我們可以捕捉這個(gè)異常矛绘,這個(gè)異常的參數(shù)定義可以查看開發(fā)文檔如下
所以我們可以把except Exception as e:耍休,改寫城except urllib.error.URLError as e:異常的精準(zhǔn)捕獲。
note: 做為開發(fā)人員要時(shí)刻提醒自己對異常货矮,對容錯(cuò)能力的處理羊精,讓我們開發(fā)的程序能夠更穩(wěn)定,更堅(jiān)固囚玫,讓我們的程序能在服務(wù)器上長時(shí)間運(yùn)行不宕機(jī)喧锦,當(dāng)然這也是成為一個(gè)高手的必要素質(zhì)读规。