翻譯:《實用的Python編程》03_03_Error_checking

目錄 | 上一節 (3.2 深刻函數) | [下一節 (3.4 模塊)]()html

3.3 錯誤檢查

雖然前面已經介紹了異常,但本節補充一些有關錯誤檢查和異常處理的其它細節。python

程序是如何運行失敗的

Python 不對函數參數類型或值進行檢查或者校驗。函數能夠處理與函數內部語句兼容的任何數據。git

def add(x, y):
    return x + y

add(3, 4)               # 7
add('Hello', 'World')   # 'HelloWorld'
add('3', '4')           # '34'

若是函數中有錯誤,它們將(做爲異常)在運行時出現。github

def add(x, y):
    return x + y

>>> add(3, '4')
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +:
'int' and 'str'
>>>

爲了驗證代碼,強烈建議進行測試(稍後介紹)。編程

異常

異經常使用於發出錯誤信號。segmentfault

要本身觸發異常,請使用 raise 語句:安全

if name not in authorized:
    raise RuntimeError(f'{name} not authorized')

要捕獲異常,請使用 try-except 語句:函數

try:
    authenticate(username)
except RuntimeError as e:
    print(e)

異常處理

異常傳遞到第一個匹配的 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()

要處理異常,請將語句放到 except 塊裏面。 except 塊裏面能夠添加要處理該錯誤的任何語句。ui

def grok(): ...
    raise RuntimeError('Whoa!')

def bar():
    try:
      grok()
    except RuntimeError as e:   # Exception caught here
        statements              # Use this statements
        statements
        ...

bar()

異常處理以後,從 try-except 以後的第一個語句繼續執行。

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()

內置異常

有很是多的內建異常。一般,異常名稱代表出了什麼問題(例如,由於提供錯誤的值而觸發 ValueError)。下述列表不是一份詳盡的清單,請訪問 文檔 以獲取更多信息。

ArithmeticError
AssertionError
EnvironmentError
EOFError
ImportError
IndexError
KeyboardInterrupt
KeyError
MemoryError
NameError
ReferenceError
RuntimeError
SyntaxError
SystemError
TypeError
ValueError

異常值

異常具備一個關聯值。它包含有關錯誤的更明確的信息。

raise RuntimeError('Invalid user name')

這個值是異常實例的一部分,它被放置在提供給 except 的變量中。

try:
    ...
except RuntimeError as e:   # `e` holds the exception raised
    ...

e 是異常類型的一個實例。可是,當打印的時候,它一般看起來像一個字符串。

except RuntimeError as e:
    print('Failed : Reason', e)

捕獲多個異常

可使用多個 except 塊捕獲不一樣類型的異常:

try:
  ...
except LookupError as e:
  ...
except RuntimeError as e:
  ...
except IOError as e:
  ...
except KeyboardInterrupt as e:
  ...

或者,若是處理不一樣異常的語句是相同的,則能夠對它們進行分組:

try:
  ...
except (IOError,LookupError,RuntimeError) as e:
  ...

捕獲全部的異常

要捕獲全部的異常,請使用 Exception 。以下所示:

try:
    ...
except Exception:       # DANGER. See below
    print('An error occurred')

一般,像這樣編寫代碼是個壞主意,由於這說明不知道程序爲何會失敗。

捕獲異常的錯誤方式

這裏是一個使用異常的錯誤方式。

try:
    go_do_something()
except Exception:
    print('Computer says no')

這將捕獲全部可能的錯誤,而且,當代碼由於某些根本沒想到的緣由(如卸載 Python 模塊等)運行失敗時,可能沒法進行調試。

更好的方式

若是想要捕獲全部的錯誤,這有一個更明智的方法。

try:
    go_do_something()
except Exception as e:
    print('Computer says no. Reason :', e)

它報告了失敗的明確緣由。當編寫捕獲全部可能異常的代碼時,擁有查看/報告錯誤的機制幾乎老是一個好主意。

不過,一般來講,最好在合理的範圍內儘可能窄地捕獲異常。僅捕獲能處理的異常。讓其它錯誤經過——也許其它代碼能夠處理。

從新觸發異常

使用 raise 傳遞捕獲的錯誤。

try:
    go_do_something()
except Exception as e:
    print('Computer says no. Reason :', e)
    raise

這容許你採起措施(例如:記錄日誌)並將錯誤傳遞給調用者。

異常的最佳實踐

不要捕獲異常,而是失敗發生時「中止運行,發出預警」(Fail fast and loud)。若是重要的話,別人會處理的。只有你是那我的的時候才捕獲異常。即,只捕獲能夠恢復並正常運行的錯誤。

finally 語句

finally 語句指定不管是否發生異常都必須運行的代碼。

lock = Lock()
...
lock.acquire()
try:
    ...
finally:
    lock.release()  # this will ALWAYS be executed. With and without exception.

一般使用 finally 語句安全地管理資源(尤爲是鎖,文件等)。

with 語句

在現代代碼中,try-finally 語句一般被 with 語句取代。

lock = Lock()
with lock:
    # lock acquired
    ...
# lock released

一個更熟悉的例子:

with open(filename) as f:
    # Use the file
    ...
# File closed

with 語句定義資源的使用上下文。當執行離開上下文時,資源被釋放。with 語句僅適用於通過專門編程以支持它的某些對象。

練習

練習 3.8:觸發異常

在上一節中編寫的 parse_csv() 函數容許選擇用戶指定的列,可是隻有輸入數據文件具備列標題時纔會生效。

請修改代碼,以便在同時傳遞 selecthas_headers=False 參數時觸發異常。例如:

>>> 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
>>>

添加此檢查後,你可能會問是否應該在函數中執行其它類型的完整性檢查。例如,檢查文件名是字符串,列表仍是其它類型?

通常來講,最好是跳過此類測試,輸入錯誤的時候讓程序運行失敗。回溯信息會指出問題的根源,而且幫助調試。

添加上述檢查的主要緣由是爲了不在無心義的模式下運行代碼(例如,使用要求列標題的特性,可是同時指定沒有標題)。

這代表調用代碼部分出現一個編程錯誤。檢查「不該發生」的狀況一般是個好主意。

練習 3.9:捕獲異常

你編寫的 parse_csv() 函數用於處理文件的所有內容。可是,在現實世界中,輸入文件可能包含損壞的數據,丟失的數據或者髒數據。嘗試下面這個實驗:

>>> 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: ''
>>>

請修改 parse_csv() 函數以便捕獲全部在記錄建立期間生成的 ValueError 異常,併爲沒法轉換的行打印警告消息。

錯誤消息應該包括行號以及有關失敗緣由的信息。要測試函數,嘗試讀取上面的 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}]
>>>

練習 3.10:隱藏錯誤

請修改 parse_csv()函數,以便用戶明確須要時能夠隱藏解析的錯誤消息,例如:

>>> 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}]
>>>

在大部分的程序中,錯誤處理是最難作好的事情之一。通常來講,不該該默默地忽略錯誤。相反,最好是報告問題,而且讓用戶選擇是否隱藏錯誤信息(若是它們選擇這樣作)。

目錄 | 上一節 (3.2 深刻函數) | [下一節 (3.4 模塊)]()

注:完整翻譯見 https://github.com/codists/practical-python-zh

相關文章
相關標籤/搜索