[譯]The Python Tutorial#Errors and Exceptions
到現(xiàn)在為止都沒有過多介紹錯(cuò)誤信息献汗,但是已經(jīng)在一些示例中使用過錯(cuò)誤信息。Python至少有兩種類型的錯(cuò)誤:語法錯(cuò)誤以及異常
8.1 Syntax Errors
語法錯(cuò)誤,也稱解析錯(cuò)誤,是Python初學(xué)者經(jīng)常抱怨的問題。
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax
Python解析器重復(fù)打印錯(cuò)誤行弦追,并且展示一個(gè)小箭頭,箭頭指向錯(cuò)誤行中錯(cuò)誤最早被偵測到的位置花竞。錯(cuò)誤由箭頭前面的語句導(dǎo)致(至少偵測到是這樣的):以上示例中劲件,錯(cuò)誤在print()
函數(shù)被偵測到,因?yàn)樵谒暗拿疤?hào):
缺失了约急。文件名和行號(hào)也被打印出來零远,當(dāng)程序來自腳本時(shí)可以方便定位問題。
8.2 Exceptions
即使語句或者表達(dá)式在語法上是正確的厌蔽,但是在執(zhí)行的時(shí)候也可能會(huì)發(fā)生錯(cuò)誤牵辣。在執(zhí)行期間偵測到的問題叫做異常,異常不是絕對(duì)的致命錯(cuò)誤:接下來會(huì)介紹如何在Python中處理異常奴饮。然而大多數(shù)異常都不會(huì)被程序處理纬向,它們最終會(huì)成為錯(cuò)誤信息择浊,展示如下:
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly
最后一行錯(cuò)誤信息指出發(fā)生了什么。異常有不同類型逾条,類型也作為錯(cuò)誤信息的一部分打印出來:以上示例中的類型有除零異常ZeroDivisionError
, 名字異常NameError
以及類型異常TypeError
近她。作為異常類型打印的字符串是built-in異常的名字。對(duì)于所有built-in異常來說都是如此膳帕,盡管這個(gè)約定很有用,但是并不是所有用戶自定義異常都會(huì)遵守薇缅。標(biāo)準(zhǔn)異常名字是built-in標(biāo)識(shí)符(不是保留字)危彩。
剩余行基于異常類型展示了詳細(xì)的信息以及異常發(fā)生的原因。
前面部分錯(cuò)誤信息以堆椨捐耄回溯的方式汤徽,展示異常發(fā)生的上下文信息。通常堆椌淖回溯中列出了源代碼行谒府;然而,當(dāng)程序是從標(biāo)準(zhǔn)輸入中讀取時(shí)浮毯,不會(huì)展示行完疫。
Built-in異常列舉了所有built-in異常以及它們的意義。
8.3 Handling Exceptions
Python程序允許處理指定異常债蓝。以下程序要求用戶循環(huán)輸入壳鹤,直到輸入為有效整數(shù)停止,但是也可以中斷程序(使用Control-C
或者其他操作系統(tǒng)支持的手段)饰迹;注意用戶誘發(fā)的中斷會(huì)拋出KeyboardInterrupt異常芳誓。
while True:
try:
x = int(input("Please enter a number: "))
break
except ValueError:
print("Oops! That was no valid number. Try again...")
try
語句的執(zhí)行順序?yàn)椋?/p>
- 首先,執(zhí)行在
try
和except
之間的try子句 - 如果沒有異常發(fā)生啊鸭,跳過except子句锹淌,
try
語句執(zhí)行完畢 - 如果try子句執(zhí)行期間有異常發(fā)生,該子句內(nèi)剩余語句被跳過赠制。接下來如果拋出的異常類型可以與關(guān)鍵字
except
后的異常匹配赂摆,那么except子句執(zhí)行,接著繼續(xù)執(zhí)行try
語句后的語句钟些。 - 如果異常發(fā)生但是沒有匹配到except后的異常库正,那么異常拋到外層
try
語句;如果異常得不到處理厘唾,該異常成為未處理異常褥符,導(dǎo)致程序終止并且像以上示例一樣打印信息。
一個(gè)try
語句可以有多個(gè)except子句抚垃,為不同的異常指定處理方法喷楣。至多只有一個(gè)except子句會(huì)被執(zhí)行趟大。處理程序只會(huì)處理發(fā)生在對(duì)應(yīng)try子句中的異常,而不會(huì)處理發(fā)生在同一try
語句中其他處理程序中的異常铣焊。一個(gè)except子句可以使用帶括號(hào)的元組列出多個(gè)異常逊朽,就像這樣:
... except (RuntimeError, TypeError, NameError):
... pass
except
子句中的類可以匹配類型是其子類或者它自己類型的異常(但反過來不行,except子句中的子類不會(huì)匹配父類異常)曲伊。例如以下代碼會(huì)依次打印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")
注意以上的except子句若反過來寫(except B
在前)叽讳,那么將會(huì)打印B, B, B ——第一個(gè)except子句的匹配被觸發(fā)。
最后一個(gè)except子句可以省略異常名字作為通配符坟募。因?yàn)檫@樣做會(huì)隱藏其他的真實(shí)程序錯(cuò)誤岛蚤,所有要謹(jǐn)慎使用。也可以用來打印錯(cuò)誤信息并且重新拋出(允許調(diào)用者處理異常):
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise
try
...except
語句有一個(gè)可選的else子句懈糯,該子句只能出現(xiàn)在所有except子句之后涤妒。若要求try子句沒有拋出異常時(shí)必須執(zhí)行一段代碼,使用else子句很有用(譯注:else
子句會(huì)被return, break, continue
跳過)赚哗。例如:
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()
使用 else
子句比在 try
子句中附加代碼要好她紫,因?yàn)檫@樣可以避免 try
... except
意外的捕獲的本不屬于它們保護(hù)的那些代碼拋出的異常。
異常發(fā)生時(shí)屿储,可以攜帶與之關(guān)聯(lián)的值贿讹,也稱作異常參數(shù)。異常參數(shù)是否可以存在以及其類型取決于異常類型够掠。
except子句可以在異常名字后指定變量围详。該變量綁定到異常實(shí)例,異常參數(shù)存儲(chǔ)在instance.args
屬性中祖屏。方便起見助赞,異常實(shí)例定義了__str__()
以便異常參數(shù)可以直接打印,而不是使用.args
引用袁勺。也可以首先實(shí)例化異常雹食,并在拋出它之前加入任何想要的參數(shù):
>>> 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
對(duì)于未處理異常來說,如果它有參數(shù)期丰,那么參數(shù)將在錯(cuò)誤信息的后面部分(詳細(xì)信息部分)打印群叶。
異常處理程序不僅僅會(huì)處理即時(shí)發(fā)生在try子句中的異常,還會(huì)處理try子句中直接或者間接調(diào)用的函數(shù)中發(fā)生的異常钝荡。例如:
>>> def this_fails():
... x = 1/0
...
>>> try:
... this_fails()
... except ZeroDivisionError as err:
... print('Handling run-time error:', err)
...
Handling run-time error: division by zero
8.4 Raising Exceptions
raise語句允許程序員強(qiáng)制發(fā)生異常街立。例如:
>>> raise NameError('HiThere')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: HiThere
raise
的唯一參數(shù)指定要拋出的異常。參數(shù)必須是異常實(shí)例或者異常類(繼承自Exception類的子類)埠通。如果參數(shù)是異常類型具篇,會(huì)隱式使用無參方式調(diào)用異常的構(gòu)造器初始化一個(gè)異常實(shí)例:
raise ValueError # shorthand for 'raise ValueError()'
如果需要捕獲異常但是不處理略步,一種更簡單形式的raise
語句允許重新拋出這個(gè)異常:
>>> 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
8.5 User-defined Exception
程序中可以通過創(chuàng)建新異常類的方式提出自己的異常(參見Classes
獲取Python類的更多信息)。異常必須直接或者間接繼承自Exception
類芹务。
自定義異常類擁有其他類的功能翠桦,但通常需要保持其簡潔性,只提供幾個(gè)供異常處理程序提取錯(cuò)誤信息的屬性。需要?jiǎng)?chuàng)建一個(gè)拋出若干不同錯(cuò)誤的模塊時(shí),比較好的實(shí)踐是码撰,為定義在這個(gè)模塊中的異常創(chuàng)建父類,由子類創(chuàng)建對(duì)應(yīng)不同錯(cuò)誤的具體異常:
class Error(Exception):
"""Base class for exceptions in this module."""
pass
class InputError(Error):
"""Exception raised for errors in the input.
Attributes:
expression -- input expression in which the error occurred
message -- explanation of the error
"""
def __init__(self, expression, message):
self.expression = expression
self.message = message
class TransitionError(Error):
"""Raised when an operation attempts a state transition that's not
allowed.
Attributes:
previous -- state at beginning of transition
next -- attempted new state
message -- explanation of why the specific transition is not allowed
"""
def __init__(self, previous, next, message):
self.previous = previous
self.next = next
self.message = message
與標(biāo)準(zhǔn)異常類類似个盆,大多數(shù)異常的名字都以"Error"結(jié)尾脖岛。
許多標(biāo)準(zhǔn)模塊都定義了自己的異常,這些異常對(duì)應(yīng)模塊中定義的函數(shù)中可能發(fā)生的錯(cuò)誤颊亮。更多信息參考Classes柴梆。
8.6 Defining Clean-up Actions
try
語句有可選的在任何情況下都會(huì)執(zhí)行的子句,可用于定義清理動(dòng)作编兄。例如:
>>> try:
... raise KeyboardInterrupt
... finally:
... print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
無論是否有異常發(fā)生,finally子句在離開try
語句之前總是會(huì)執(zhí)行声登。當(dāng)try
子句中有異常發(fā)生并且沒有被except
子句處理(或者異常發(fā)生在except
子句或else
子句)狠鸳,finally
子句執(zhí)行之后,這些異常會(huì)重新拋出悯嗓。當(dāng)try
語句的其他子句通過break, continue
或者return
語句離開時(shí)件舵,finally
子句也會(huì)執(zhí)行。以下是較為復(fù)雜的示例:
>>> 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'
正如所見的那樣脯厨,finally
子句在任何情況下都會(huì)執(zhí)行铅祸。兩個(gè)字符串相除產(chǎn)生的TypeError
異常沒有被except
子句處理,因此在finally
子句執(zhí)行完畢之后被重新拋出合武。
在實(shí)踐應(yīng)用中临梗,finally
子句用來釋放外部資源(比如文件和網(wǎng)絡(luò)連接),不論資源的使用是否成功稼跳。
8.7 Predefined Clean-up Actions
一些對(duì)象定義了標(biāo)準(zhǔn)的清理動(dòng)作盟庞,當(dāng)不再需要這些對(duì)象時(shí),會(huì)執(zhí)行清理動(dòng)作汤善,而不論使用對(duì)象的操作是否成功什猖。以下示例讀取文件并打印內(nèi)容到顯示器:
for line in open("myfile.txt"):
print(line, end="")
這段代碼的問題是:當(dāng)代碼執(zhí)行完畢后,文件會(huì)保留打開狀態(tài)一段不確定的時(shí)間红淡。這在簡單的腳本中不是什么大問題不狮,但是在大型的應(yīng)用程序中會(huì)出問題。with
語句使得像文件一樣的對(duì)象可以被立即準(zhǔn)確回收在旱。
with open("myfile.txt") as f:
for line in f:
print(line, end="")
語句執(zhí)行后摇零,即使在執(zhí)行代碼時(shí)遇到問題,文件f總是會(huì)被關(guān)閉桶蝎。像文件一樣提供預(yù)定義清理動(dòng)作的對(duì)象會(huì)在其說明文檔中指示這點(diǎn)遂黍。