Python 錯誤和異常
0. 只看一眼:
- 格式:
try:
...
except Error1:
...
except Error2:
...
...
else:
...
finally:
...
with open('/path/to/file', 'r') as f:
print(f.read())
1.異常
- 程序語句未必正確,在嘗試執行時會引發錯誤,這時就會拋出異常。異常可以被寫程序處理,比如可以處理成即使遇到某個異常也讓程序繼續執行而不是斷掉。
2.錯誤信息
- 錯誤信息的前部分會以堆棧形式顯示發生異常時的上下文。
- 錯誤信息的最後一行會告訴我們程序遇到什麼類型的錯誤。
- 異常有不同類型,類型名稱會在錯誤信息中打印出來。
- Python的異常其實也是class,所有的異常類型都繼承自BaseException
3.處理異常
3.1 寫法1(一般格式):
try:
...
except ValueError: #except後是錯誤類型
...
注意:
- 如果發生的異常和except字句中的異常不匹配,則將傳遞到外部的try語句中。如果沒有找到處理程序,則它是一個未處理異常,停止執行並顯示錯誤信息。
- 一個try後可跟多個except字句,但是最多會執行一個處理程序。
3.2 寫法2:
try:
...
except(RuntimeError,TypeError,NameError):
...
3.3 關於except中的子類與基類:
看下面兩個代碼:
#派生關係: Exception->B->C->D
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
'''
運行結果:
B
C
D
'''
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except B: #把 B 移到前面
print("B")
except D:
print("D")
except C:
print("C")
'''
運行結果:
B
B
B
'''
總結:
在處理具有繼承關係的錯誤類型時,在except安排順序時,如果把基類放在子類前面,基類對於子類錯誤有“屏蔽的作用”,所以一般排順序最好按先子類,後基類的順序。
3.4 寫法3
最後可有一個空的except,如果有異常,且跟前面的異常沒有配上,那麼會執行這個except。
try:
...
except OSError:
...
except:
print("Unexpected error:") #可以打印錯誤信息
raise #可以重新拋出異常
- 上圖可看出,有了異常處理,程序繼續執行下去了。但是這樣的方法很容易隱藏編程錯誤(還不太知道爲啥)
- 在這裏我們加了raise,拋出異常,程序運行不下去了。(raise語句如果不帶參數,就會把當前錯誤原樣拋出)
3.5 寫法4
- 可有else子句,在使用時必須放在所有的except後。
- 好處: 使用 else 子句比在 try 子句中附加代碼要好,因爲這樣可以避免 try … except 意外的截獲本來不屬於它們保護的那些代碼拋出的異常。
https://www.zhihu.com/question/266526582
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except OSError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()
3.6 傳入變量
- except 子句可以在異常名稱後面指定一個變量。這個變量和一個異常實例綁定,它的參數存儲在 instance.args 中。爲了方便起見,異常實例定義了 str() ,因此可以直接打印參數而無需引用 .args 。也可以在拋出之前首先實例化異常,並根據需要向其添加任何屬性。
- 這樣做的好處:
可以用於更好地定位錯誤,比如說前面有一個for循環,在處理一堆對象,如果僅raise Exception,那麼我們不太容易找到這一堆對象是誰出錯了。而傳個參數,比如傳是第幾個對象(一個數字),或者對象名,就可以比較容易地定位到出錯位置。
>>> try:
... raise Exception('spam', 'eggs')
... except Exception as inst:
... print(type(inst)) # the exception instance
... print(inst.args) # arguments stored in .args
... print(inst) # __str__ allows args to be printed directly,
... # but may be overridden in exception subclasses
... x, y = inst.args # unpack args
... print('x =', x)
... print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
4.拋出異常
4.1 raise
- raise允許程序員強制發生指定的異常。
- raise唯一的參數就是要拋出的異常,這個參數必須是一個異常實例或一個異常類(派生自Exception)。如果傳遞的是一個異常類,它將通過調用沒有參數的構造函數來隱式實例化。
raise NameError('Hi)
raise ValueError #shorthand for 'raise ValueError()'
- 如果需要確定是否引發了異常而不打算處理,可以直接(???):
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: HiThere
5.自定義異常
- 異常通常應該直接或間接地從Exception類派生。
- 可以定義異常類,它可以執行任何其他類可以執行的任何操作,但通常保持簡單,通常只提供許多屬性,這些屬性允許處理程序爲異常提取有關錯誤的信息。在創建可能引發多個不同錯誤的模塊時,通常的做法是爲該模塊定義的異常創建基類,併爲不同錯誤條件創建特定異常類的子類:
- 寫法等需要再詳細補充
6.定義清理操作-finally
- finally會作爲try語句結束前最後一項任務被執行,無論try語句是否產生了異常都可以被執行。
- 好處: 對於釋放外部資源非常有用,比如關閉文件。
- 如果在執行 try 子句期間發生了異常,該異常可由一個 except 子句進行處理。 如果異常沒有被 except 子句所處理,則該異常會在 finally 子句執行之後被重新引發。
- 異常也可能在 except 或 else 子句執行期間發生。 同樣地,該異常會在 finally 子句執行之後被重新引發。
- 如果在執行 try 語句時遇到一個 break, continue 或 return 語句,則 finally 子句將在執行 break, continue 或 return 語句之前被執行。
- 如果 finally 子句中包含一個 return 語句,則 finally 子句的 return 語句將在執行 try 子句的 return 語句之前取代後者被執行。
>>> def bool_return():
... try:
... return True
... finally:
... return False
...
>>> bool_return()
False
>>> def divide(x, y):
... try:
... result = x / y
... except ZeroDivisionError:
... print("division by zero!")
... else:
... print("result is", result)
... finally:
... print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
7. 預定義的清理操作
- 我們可以這樣處理文件的打開與關閉:
我們不能 直接f.open(),f.read(),f.close(),由於文件讀寫時都有可能產生IOError,一旦出錯,後面的f.close()就不會調用。所以,爲了保證無論是否出錯都能正確地關閉文件,我們可以使用try … finally來實現:
try:
f = open('/path/to/file', 'r')
print(f.read())
finally:
if f:
f.close()
- 也可以:
with open('/path/to/file', 'r') as f:
print(f.read())
with允許像文件這樣的對象能夠以一種確保它們得到及時和正確的清理的方式使用