翻譯:《實(shí)用的Python編程》03_03_Error_checking
目錄 | 上一節(jié) (3.2 深入函數(shù)) | 下一節(jié) (3.4 模塊)
3.3 錯(cuò)誤檢查
雖然前面已經(jīng)介紹了異常,但本節(jié)補(bǔ)充一些有關(guān)錯(cuò)誤檢查和異常處理的其它細(xì)節(jié)。
程序是如何運(yùn)行失敗的
Python 不對(duì)函數(shù)參數(shù)類(lèi)型或值進(jìn)行檢查或者校驗(yàn)。函數(shù)可以處理與函數(shù)內(nèi)部語(yǔ)句兼容的任何數(shù)據(jù)。
def add(x, y):
return x + y
add(3, 4) # 7
add('Hello', 'World') # 'HelloWorld'
add('3', '4') # '34'
如果函數(shù)中有錯(cuò)誤,它們將(作為異常)在運(yùn)行時(shí)出現(xiàn)。
def add(x, y):
return x + y
>>> add(3, '4')
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +:
'int' and 'str'
>>>
為了驗(yàn)證代碼,強(qiáng)烈建議進(jìn)行測(cè)試(稍后介紹)。
異常
異常用于發(fā)出錯(cuò)誤信號(hào)。
要自己觸發(fā)異常,請(qǐng)使用 raise 語(yǔ)句:
if name not in authorized:
raise RuntimeError(f'{name} not authorized')
要捕獲異常,請(qǐng)使用 try-except 語(yǔ)句:
try:
authenticate(username)
except RuntimeError as e:
print(e)
異常處理
異常傳遞到第一個(gè)匹配的 except :
def grok():
...
raise RuntimeError('Whoa!') # Exception raised here
def spam():
grok() # Call that will raise exception
def bar():
try:
spam()
except RuntimeError as e: # Exception caught here
...
def foo():
try:
bar()
except RuntimeError as e: # Exception does NOT arrive here
...
foo()
要處理異常,請(qǐng)將語(yǔ)句放到 except 塊里面。 except 塊里面可以添加要處理該錯(cuò)誤的任何語(yǔ)句。
def grok(): ...
raise RuntimeError('Whoa!')
def bar():
try:
grok()
except RuntimeError as e: # Exception caught here
statements # Use this statements
statements
...
bar()
異常處理之后,從 try-except 之后的第一個(gè)語(yǔ)句繼續(xù)執(zhí)行。
def grok(): ...
raise RuntimeError('Whoa!')
def bar():
try:
grok()
except RuntimeError as e: # Exception caught here
statements
statements
...
statements # Resumes execution here
statements # And continues here
...
bar()
內(nèi)置異常
有非常多的內(nèi)建異常。通常,異常名稱(chēng)表明出了什么問(wèn)題(例如,因?yàn)樘峁╁e(cuò)誤的值而觸發(fā) ValueError)。下述列表不是一份詳盡的清單,請(qǐng)?jiān)L問(wèn) 文檔 以獲取更多信息。
ArithmeticError
AssertionError
EnvironmentError
EOFError
ImportError
IndexError
KeyboardInterrupt
KeyError
MemoryError
NameError
ReferenceError
RuntimeError
SyntaxError
SystemError
TypeError
ValueError
異常值
異常具有一個(gè)關(guān)聯(lián)值。它包含有關(guān)錯(cuò)誤的更明確的信息。
raise RuntimeError('Invalid user name')
這個(gè)值是異常實(shí)例的一部分,它被放置在提供給 except 的變量中。
try:
...
except RuntimeError as e: # `e` holds the exception raised
...
e 是異常類(lèi)型的一個(gè)實(shí)例。但是,當(dāng)打印的時(shí)候,它通常看起來(lái)像一個(gè)字符串。
except RuntimeError as e:
print('Failed : Reason', e)
捕獲多個(gè)異常
可以使用多個(gè) except 塊捕獲不同類(lèi)型的異常:
try:
...
except LookupError as e:
...
except RuntimeError as e:
...
except IOError as e:
...
except KeyboardInterrupt as e:
...
或者,如果處理不同異常的語(yǔ)句是相同的,則可以對(duì)它們進(jìn)行分組:
try:
...
except (IOError,LookupError,RuntimeError) as e:
...
捕獲所有的異常
要捕獲所有的異常,請(qǐng)使用 Exception 。如下所示:
try:
...
except Exception: # DANGER. See below
print('An error occurred')
通常,像這樣編寫(xiě)代碼是個(gè)壞主意,因?yàn)檫@說(shuō)明不知道程序?yàn)槭裁磿?huì)失敗。
捕獲異常的錯(cuò)誤方式
這里是一個(gè)使用異常的錯(cuò)誤方式。
try:
go_do_something()
except Exception:
print('Computer says no')
這將捕獲所有可能的錯(cuò)誤,并且,當(dāng)代碼因?yàn)槟承└緵](méi)想到的原因(如卸載 Python 模塊等)運(yùn)行失敗時(shí),可能無(wú)法進(jìn)行調(diào)試。
更好的方式
如果想要捕獲所有的錯(cuò)誤,這有一個(gè)更明智的方法。
try:
go_do_something()
except Exception as e:
print('Computer says no. Reason :', e)
它報(bào)告了失敗的明確原因。當(dāng)編寫(xiě)捕獲所有可能異常的代碼時(shí),擁有查看/報(bào)告錯(cuò)誤的機(jī)制幾乎總是一個(gè)好主意。
不過(guò),通常來(lái)說(shuō),最好在合理的范圍內(nèi)盡量窄地捕獲異常。僅捕獲能處理的異常。讓其它錯(cuò)誤通過(guò)——也許其它代碼可以處理。
重新觸發(fā)異常
使用 raise 傳遞捕獲的錯(cuò)誤。
try:
go_do_something()
except Exception as e:
print('Computer says no. Reason :', e)
raise
這允許你采取措施(例如:記錄日志)并將錯(cuò)誤傳遞給調(diào)用者。
異常的最佳實(shí)踐
不要捕獲異常,而是失敗發(fā)生時(shí)“停止運(yùn)行,發(fā)出預(yù)警”(Fail fast and loud)。如果重要的話(huà),別人會(huì)處理的。只有你是那個(gè)人的時(shí)候才捕獲異常。即,只捕獲可以恢復(fù)并正常運(yùn)行的錯(cuò)誤。
finally 語(yǔ)句
finally 語(yǔ)句指定無(wú)論是否發(fā)生異常都必須運(yùn)行的代碼。
lock = Lock()
...
lock.acquire()
try:
...
finally:
lock.release() # this will ALWAYS be executed. With and without exception.
通常使用 finally 語(yǔ)句安全地管理資源(尤其是鎖,文件等)。
with 語(yǔ)句
在現(xiàn)代代碼中,try-finally 語(yǔ)句通常被 with 語(yǔ)句取代。
lock = Lock()
with lock:
# lock acquired
...
# lock released
一個(gè)更熟悉的例子:
with open(filename) as f:
# Use the file
...
# File closed
with 語(yǔ)句定義資源的使用上下文。當(dāng)執(zhí)行離開(kāi)上下文時(shí),資源被釋放。with 語(yǔ)句僅適用于經(jīng)過(guò)專(zhuān)門(mén)編程以支持它的某些對(duì)象。
練習(xí)
練習(xí) 3.8:觸發(fā)異常
在上一節(jié)中編寫(xiě)的 parse_csv() 函數(shù)允許選擇用戶(hù)指定的列,但是只有輸入數(shù)據(jù)文件具有列標(biāo)題時(shí)才會(huì)生效。
請(qǐng)修改代碼,以便在同時(shí)傳遞 select 和 has_headers=False 參數(shù)時(shí)觸發(fā)異常。例如:
>>> parse_csv('Data/prices.csv', select=['name','price'], has_headers=False)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "fileparse.py", line 9, in parse_csv
raise RuntimeError("select argument requires column headers")
RuntimeError: select argument requires column headers
>>>
添加此檢查后,你可能會(huì)問(wèn)是否應(yīng)該在函數(shù)中執(zhí)行其它類(lèi)型的完整性檢查。例如,檢查文件名是字符串,列表還是其它類(lèi)型?
一般來(lái)說(shuō),最好是跳過(guò)此類(lèi)測(cè)試,輸入錯(cuò)誤的時(shí)候讓程序運(yùn)行失敗?;厮菪畔?huì)指出問(wèn)題的根源,并且?guī)椭{(diào)試。
添加上述檢查的主要原因是為了避免在無(wú)意義的模式下運(yùn)行代碼(例如,使用要求列標(biāo)題的特性,但是同時(shí)指定沒(méi)有標(biāo)題)。
這表明調(diào)用代碼部分出現(xiàn)一個(gè)編程錯(cuò)誤。檢查“不應(yīng)發(fā)生”的情況通常是個(gè)好主意。
練習(xí) 3.9:捕獲異常
你編寫(xiě)的 parse_csv() 函數(shù)用于處理文件的全部?jī)?nèi)容。但是,在現(xiàn)實(shí)世界中,輸入文件可能包含損壞的數(shù)據(jù),丟失的數(shù)據(jù)或者臟數(shù)據(jù)。嘗試下面這個(gè)實(shí)驗(yàn):
>>> portfolio = parse_csv('Data/missing.csv', types=[str, int, float])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "fileparse.py", line 36, in parse_csv
row = [func(val) for func, val in zip(types, row)]
ValueError: invalid literal for int() with base 10: ''
>>>
請(qǐng)修改 parse_csv() 函數(shù)以便捕獲所有在記錄創(chuàng)建期間生成的 ValueError 異常,并為無(wú)法轉(zhuǎn)換的行打印警告消息。
錯(cuò)誤消息應(yīng)該包括行號(hào)以及有關(guān)失敗原因的信息。要測(cè)試函數(shù),嘗試讀取上面的 Data/missing.csv 文件,例如:
>>> portfolio = parse_csv('Data/missing.csv', types=[str, int, float])
Row 4: Couldn't convert ['MSFT', '', '51.23']
Row 4: Reason invalid literal for int() with base 10: ''
Row 7: Couldn't convert ['IBM', '', '70.44']
Row 7: Reason invalid literal for int() with base 10: ''
>>>
>>> portfolio
[{'price': 32.2, 'name': 'AA', 'shares': 100}, {'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 40.37, 'name': 'GE', 'shares': 95}, {'price': 65.1, 'name': 'MSFT', 'shares': 50}]
>>>
練習(xí) 3.10:隱藏錯(cuò)誤
請(qǐng)修改 parse_csv()函數(shù),以便用戶(hù)明確需要時(shí)可以隱藏解析的錯(cuò)誤消息,例如:
>>> portfolio = parse_csv('Data/missing.csv', types=[str,int,float], silence_errors=True)
>>> portfolio
[{'price': 32.2, 'name': 'AA', 'shares': 100}, {'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 40.37, 'name': 'GE', 'shares': 95}, {'price': 65.1, 'name': 'MSFT', 'shares': 50}]
>>>
在大部分的程序中,錯(cuò)誤處理是最難做好的事情之一。一般來(lái)說(shuō),不應(yīng)該默默地忽略錯(cuò)誤。相反,最好是報(bào)告問(wèn)題,并且讓用戶(hù)選擇是否隱藏錯(cuò)誤信息(如果它們選擇這樣做)。
目錄 | 上一節(jié) (3.2 深入函數(shù)) | 下一節(jié) (3.4 模塊)
注:完整翻譯見(jiàn) https://github.com/codists/practical-python-zh
浙公網(wǎng)安備 33010602011771號(hào)