blog
blog copied to clipboard
Python之旅:第八章-异常
Table of Contents generated with DocToc
-
Python之旅:第八章 异常
- 异常是什么
-
让事情沿你指定的轨道出错
- raise语句
- 自定义的异常类
-
捕获异常
- 不用提供参数
- 多个except子句
- 一箭双雕
- 捕获对象
- 一网打尽
- 万事大吉时
- 最后
- 异常和函数
- 异常之禅
- 不那么异常的情况
Python之旅:第八章 异常
异常是什么
Python使用异常对象来表示异常状态,并在遇到错误时引发异常,异常对象未被处理(捕获)时,程序将终止并显示一条错误信息:
>>> 1 / 0
Traceback (most recent call last):
File "<pyshell#147>", line 1, in <module>
1 / 0
ZeroDivisionError: division by zero
每个异常都是ZeroDivisionError
类的实例,我们可以使用各种方式引发和捕获这些实例,从而采取措施而不是放任整个程序失败。
让事情沿你指定的轨道出错
raise语句
在Python中,我们可以使用raise
语句来引发异常,并将一个类(必须是Exception
类的子类)或实例作为参数,将类作为参数时,将自动创建一个实例。下面的示例使用的是内置异常类Exception
:
>>> raise Exception
Traceback (most recent call last):
File "<pyshell#148>", line 1, in <module>
raise Exception
Exception
>>> raise Exception('hyperdrive overload')
Traceback (most recent call last):
File "<pyshell#149>", line 1, in <module>
raise Exception('hyperdrive overload')
Exception: hyperdrive overload
Python内置了很多异常类,下表描述了一些重要的,这些异常类都可以用于raise
语句。
类名 | 描述 |
---|---|
Exception |
几乎所有的异常类都是从它派生而来的 |
AttributeError |
引用属性或给它赋值失败时引发 |
OSError |
操作系统不能执行指定的任务时触发,有多个子类 |
IndexError |
使用序列中不存在的索引时触发,为LookupError 的子类 |
KeyError |
使用映射中不存在的键时触发,为LookupError 的子类 |
NameError |
找不到名称(变量)时引发 |
TypeError |
将内置操作或函数用于不正确的对象时引发 |
ValueError |
将内置操作或函数用于这样的对象时引发:其类型正确但包含的值不合适 |
ZeroDivisionError |
在除法或求模运算的第二个参数为零时引发 |
自定义的异常类
虽然Python内置了大量的异常类,供我们使用,但有时也需要我们自定义异常类,创建自定义异常类,跟创建其他类一样,只是需要直接或间接的继承Exception
类(从其他的异常类派生也是可以的,因为所有的异常类都派生自Exception
类):
class SomeCustomException(Exception): pass
如果有需要,也可以在自定义异常类中添加方法。
捕获异常
在Python中,我们可以使用try/except
语句来捕获异常,比如我们有一段程序,让用户输入两个数,然后将它们相除,如果用户输入的第二个数为零,则我们需要捕获这个异常:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except ZeroDivisionError:
print('The second number can\'t be zero!')
执行结果如下,我们对这种情况友好的做了提示:
Enter the first number: 2
Enter the second number: 0
The second number can't be zero!
好像这种情况使用if
语句更简单,但如果程序很庞大,里面有多个像这样的计算任务,使用一个try/except
就可以捕获所有的错误。
注意:异常是从函数向外传播到调用函数的地方的,如果异常在这里没有被捕获,那么它将向程序的最顶层传播。
不用提供参数
捕获异常后,如果要重新引发它(继续向上传播),可调用raise
且不提供任何参数。
class MuffleCalculator:
muffled = False
def calc(self, expr):
try:
return eval(expr)
except ZeroDivisionError:
# 带有’抑制‘功能
if self.muffled:
print('Division by zero is illegal')
else:
raise
上面的例子运行如下:
>>> calculator = MuffleCalculator()
>>> calculator.calc('10 / 2')
5.0
>>> calculator.calc('10 / 0')
Traceback (most recent call last):
File "<pyshell#159>", line 1, in <module>
calculator.calc('10 / 0')
File "<pyshell#155>", line 5, in calc
return eval(expr)
File "<string>", line 1, in <module>
ZeroDivisionError: division by zero # 关闭抑制功能,捕获了异常,但继续向上传播
>>> calculator.muffled = True
>>> calculator.calc('10 / 0')
Division by zero is illegal
多个except子句
如果程序需要同时捕获多个异常,我们可以在try/except
语句中再添加一个except
子句:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except ZeroDivisionError:
print('The second number can\'t be zero!')
except TypeError:
print('That wasn\'t a number, was it?')
一箭双雕
如果我们只想使用一个except
语句来捕获多个异常呢?可以在一个元组中指定这些异常。
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except (ZeroDivisionError, TypeError, NameError):
print('Your numbers were bogus ...')
捕获对象
要在except
子句中访问异常对象本身,可使用两个二不是一个参数,这种方式可以让程序继续运行并记录错误:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except (ZeroDivisionError, TypeError) as e:
print(e)
一网打尽
如果我们不在except
语句中指定任何异常类,那么我们将可以捕获所有的异常:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except:
print('Something wrong happended ...')
使用这样的方式并不是很好,因为这样不仅会隐藏我们已知的错误,还会隐藏我们没有考虑到的错误。所以,在大多数情况下,更好的选择是使用execpt Exception as e
并对异常对象进行检查。
万事大吉时
我们还可以给try/execpt
语句加上一个else
子句,当没有异常发生时,else
里的语句将会被执行。
try:
print('A simple task')
except:
print('What? Something went wrong?')
else:
print('Ah... It went as planned.')
使用else
子句,可以实现前面的一个循环获取用户输入的示例,只有当没有引发异常时,才会跳出循环,如果用户输入错误,则会一直提示用户输入:
while True:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
value = x / y
print('x / y is ', value)
except:
print('Invalid input. Please try again.')
else:
break
运行结果如下:
Enter the first number: 1
Enter the second number: 0
Invalid input. Please try again.
Enter the first number: foo
Invalid input. Please try again.
Enter the first number: 'foo'
Invalid input. Please try again.
Enter the first number: 10
Enter the second number: 2
x / y is 5.0
我们也可以使用except Exception as e
这种方式打印更有用的错误信息:
while True:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
value = x / y
print('x / y is ', value)
except Exception as e:
print('Invalid input: ', e)
print('Please try again')
else:
break
运行结果如下:
Enter the first number: 1
Enter the second number: 0
Invalid input: division by zero
Please try again
Enter the first number: 'x'
Invalid input: invalid literal for int() with base 10: "'x'"
Please try again
Enter the first number: 10
Enter the second number: 2
x / y is 5.0
最后
最后,还有一个finally
子句,它可用于在发生异常时执行清理工作,这个子句是与try
子句配套的。
x = None
try:
x = 1 / 0
finally:
print('Clearing up ...')
del x
不管try
子句中发生什么异常,都将执行finally
子句。finally
子句也非常适合用于确保文件或网络套接字被关闭。
我们也可以在一条语句中同时包含try
、except
、finally
和else
,或其中的3个
try:
1 / 0
except NameError:
print('Unknown variable')
else:
print('That went well!')
finally:
print('Clearing up.')
异常和函数
通常如果不处理在函数中引发的异常,它就会向上传播到调用函数的地方,如果那里也未得到处理,异常将继续传播,直至到达主程序(全局作用域),如果主程序也没有异常处理,那么程序将终止并显示栈跟踪信息。
>>> def faulty():
raise Exception('Something is wrong')
>>> def ignore_exception():
faulty()
>>> def handle_exception():
try:
faulty()
except:
print('Exception handled!')
调用结果如下:
>>> ignore_exception()
Traceback (most recent call last):
File "<pyshell#178>", line 1, in <module>
ignore_exception()
File "<pyshell#175>", line 2, in ignore_exception
faulty()
File "<pyshell#173>", line 2, in faulty
raise Exception('Something is wrong')
Exception: Something is wrong
>>> handle_exception()
Exception handled!
异常之禅
如果你知道代码可能引发某种异常,且不希望出现这种异常时程序终止并系那是栈跟踪信息,可以添加必要的try/except
或try/finally
语句来处理它。
有时候你可能觉得使用if/else
更自然一些,但使用try/except
要比if/else
效率更高。例如我们要打印某人的信息,且参数传入了一个字典:
def describe_person(person):
print('Description of ', person['name'])
print('Age: ', person['age'])
if 'occupation' in person:
print('Occupation: ', person['occupation'])
像上面的这段代码,程序必须两次检查occupation
键是否存在于参数字典person
中,一次检查,一次调用。如果我们换成try/except
来完成,则效率更高:
def describe_person(person):
print('Description of ', person['name'])
print('Age: ', person['age'])
try:
print('Occupation: ', person['occupation'])
except KeyError: pass
往往在检查对象是否包含特定的属性时,try/except
就很有用。然而,这种效率方面的提升并不明显,但使用try/except
更符合Python风格,因此我们应养成多使用它的习惯。
不那么异常的情况
如果我们想在程序运行出现问题时发出警告,则可以使用模块warnings
中的函数warn
>>> from warnings import warn
>>> warn('I\'ve got a bad feeling about this.')
Warning (from warnings module):
File "__main__", line 1
UserWarning: I've got a bad feeling about this.
我们也可以使用warnings
中的函数filterwarnings
来抑制你发出的警告并指定要采取的措施
>>> from warnings import filterwarnings
>>> filterwarnings('ignore')
>>> warn('ANyone out there?')
>>> filterwarnings('error')
>>> warn('Something is very wrong!')
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
warn('Something is very wrong!')
UserWarning: Something is very wrong!
本章节完毕
本系列目录: