Python使用异常对象来表示异常状态,并在遇到错误时引发异常。异常对象未被处理(或捕获)时,程序将终止并显示一条错误消息(traceback)。
异常不仅仅只是用来显示错误消息,每个异常都是某个类的实例,你能以各种方式引发和捕获这些实例,从而逮住错误并采取措施。
1.raise语句主动引发异常
要引发异常,可使用raise语句,并将一个类(必须是Exception的子类)或实例作为参数。将类作为参数时,将自动创建一个实例。
raise Exception #引发异常但并不附带错误信息
raise Exception('出错警告!') #引发异常并附带错误信息
Traceback (most recent call last):
File "C:\Users\llj\AppData\Roaming\JetBrains\PyCharm2021.3\scratches\scratch_2.py", line 1, in <module>
raise Exception('出错警告!')
Exception: 出错警告!
raise Exception引发的是通用异常,异常的类型有许多,Python内置的异常类型见下表:
类名 描述
Exception 几乎所有的异常类都是从它派生而来的
AttributeError 引用属性或给它赋值失败时引发
OSError 操作系统不能执行指定的任务(如打开文件),有多个子类
IndexError 使用序列中不存在的索引时引发,为LookupError的子类
KeyError 使用映射中不存在的键时引发,为LookupError的子类
NameError 找不到名称(变量)时引发
SyntaxError 代码不正确时引发
TypeError 将内置操作或函数用于类型不正确的对象时引发
ValueError将内置操作或函数用于这样的对象时引发:其类型正确但包含的值不合适
ZeroDivisionError在除法或求模运算的第二个参数为零时引发
2.自定义的异常类
虽然内置异常涉及的范围很广,已经能满足许多的需求,但有时候也需要自己创建异常类。
创建异常类就像创建其他类一样,但需要直接或间接地继承Exception(这意味着从任何内置异常类派生都可以)。因此,自定义异常类的代码类似于下面这样:
class SomeCustomException(Exception):pass
3.捕获异常try/except
捕获异常可以使用try/except语句
假如创建一个让用户输入2个数并相除的程序,如果用户输入的第二个数字为0,就会引发除零异常,为了捕获异常并对错误进行处理,就可以加入try/except,这里捕获到除零异常后,处理方法是提示除数不能为零。
try:
x=int(input('请输入第一个数字'))
y=int(input('请输入第二个数字'))
print(x/y)
except ZeroDivisionError:
print('除数不能为0')
注意:异常从函数向外传播到调用函数的地方。如果在这里也没有被捕获,异常将向程序的最顶层传播。这意味着你可使用try/except来捕获其他人所编写函数引发的异常。
不用提供参数
捕获异常后,如果要重新引发它(即继续向上传播),可调用raise且不提供任何参数(也可显式地提供捕获到的异常)。
为说明这很有用,来看一个能够“抑制”异常ZeroDivisionError的计算器类。如果启用了这种功能,计算器将打印一条错误消息,而不让异常继续传播。在与用户交互的会话中使用这个计算器时,抑制异常很有用;但在程序内部使用时,引发异常是更佳的选择(此时应关闭“抑制”功能)。下面是这样一个类的代码:
class MuffledCalculator:
muffled = False
def calc(self, expr):
try:
return eval(expr)
except ZeroDivisionError:
if self.muffled:
print('Division by zero is illegal')
else:
raise
注意发生除零行为时,如果启用了“抑制”功能,方法calc将(隐式地)返回None。换而言之,如果启用了“抑制”功能,就不应依赖返回值。
下面的示例演示了这个类的用法(包括启用和关闭了抑制功能的情形):
class MuffledCalculator:
muffled = False
def calc(self, expr):
try:
return eval(expr)
except ZeroDivisionError:
if self.muffled:
print('不允许除零')
else:
raise
calculator=MuffledCalculator()
#calculator.calc('3/0')
calculator.muffled=True
calculator.calc('10/0') #不允许除零
在上面的代码中,关闭抑制功能时,捕获了异常ZeroDivisionError,但继续向上传播它。
如果异常无法被处理,在except子句中使用不带参数的raise通常是不错的选择,但有时你可能想引发别的异常,在这种情况下,导致进入except子句的异常将被作为异常上下文存储器阿里,并出现在最终的错误消息中。
这个章节没看懂,原文在《Python基础教程》第三版
捕获多种异常
如果要用一个except语句捕获多种异常,可在一个元组中指定这些异常,比如下面这个示例,用户输入字符串、其他非数字或者0,都将打印同样的错误信息。仅仅打印错误信息的帮助可能不大,另一种解决方案是不断地要求用户输入数字,直到除法运算能够正常的执行:
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子句中访问异常对象本身,可使用两个而不是一个参数。需要注意的是,即便是在你捕获多个异常时,也只向except提供了一个参数:一个元组。需要让程序运行并记录错误时这很有用。
下面的示例程序打印发生的异常并继续运行:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except (ZeroDivisionError, TypeError, NameError) 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 happened ...')
像这样捕获所有异常很危险,因为这不仅会隐藏你有心理准备的错误,还会隐藏你没有考虑过的错误。这还将捕获用户使用Ctrl+C终止执行的企图、调用函数sys.exit来终止执行的企图等。
在大多数情况下,更好的选择是使用except Exception as e并对异常对象进行检查,这样做将让不是从Exception派生而来的为数不多的异常成为漏网之鱼,其中包括SystemExit和KeyboardInterrupt,因为它们是从BaseException(Exception的超类)派生而来的。
else子句
在有些情况下,在没有出现异常时执行一个代码块很有用,可以像条件语句和循环一样,给try/except语句添加一个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
使用空的except子句可以捕获所有属于类Exception(或其子类)的异常。你不能完全确定浙江捕获所有异常,因为try/except语句中的代码可能使用旧式的字符串异常或引发并非从Exception派生而来的异常。
然而,使用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
finally子句
最后,还有finally子句,可用于在发生异常时执行清理工作。这个子句是与try子句配套的。不管try子句中发生什么异常,都将执行finally子句,并且在运行finally子句后崩溃(结束)。
x=None
try:
x=1/0
finally:
print('清理...')
del x
print(123) #这一行不会被执行
一整个异常处理代码块可以同时包含try、except、else以及finally,
except表示发生错误时做什么,else表示没有出错时执行什么操作,finally表示最终执行什么操作,finally子句无论前面是否出现异常都将会被执行。
4.异常和函数
异常和函数有着天然的联系,如果不处理函数中引发的异常,它将向上传播到调用函数的地方,如果在那里也未得到处理,异常将继续传播,直至到达主程序(全局作用域),如果主程序也没有异常处理程序,程序将终止并显示栈跟踪消息。
示例:
如你所见,faulty中引发的异常依次从faulty和ignore_exception向外传播,最终导致显示一条栈跟踪消息。调用handle_exception时,异常最终传播到handle_exception,并被这里的try/except语句处理。
5.异常之禅
异常处理并不是很复杂。如果你知道代码可能引发某种异常,且不希望出现这种异常时程序终止并显示栈跟踪消息,可添加必要的try/except或try/finally语句(或结合使用)来处理它。有时候,可使用条件语句来达成异常处理实现的目标,但这样编写出来的代码可能不那么自然,可读性也没那么高。另一方面,有些任务使用if/else完成时看似很自然,但实际上使用try/except来完成要好得多。下面来看两个示例。假设有一个字典,你要在指定的键存在时打印与之相关联的值,否则什么都不做。实现这种功能的代码可能类似于下面这样:
def describe_person(person):
print('Description of', person['name'])
print('Age:', person['age'])
if 'occupation' in person:
print('Occupation:', person['occupation'])
如果你调用这个函数,并向它提供一个包含姓名Throatwobbler Mangrove和年龄42(但不包含职业)的字典,输出将如下:
Description of Throatwobbler Mangrove
Age: 42
这段代码很直观,但效率不高(虽然这里的重点是代码简洁),因为它必须两次查找’occupation’键:一次检查这个键是否存在(在条件中),另一次获取这个键关联的值,以便将其打印出来。下面是另一种解决方案:
def describe_person(person):
print('Description of', person['name'])
print('Age:', person['age'])
try:
print('Occupation:', person['occupation'])
except KeyError:
pass
在这里,函数直接假设存在’occupation’键。如果这种假设正确,就能省点事:直接获取并打印值,而无需检查这个键是否存在。如果这个键不存在,将引发KeyError异常,而except子句将捕获这个异常。你可能发现,检查对象是否包含特定的属性时,try/except也很有用。例如,假设你要检查一个对象是否包含属性write,可使用类似于下面的代码:
try:
obj.write
except AttributeError:
print('The object is not writeable')
else:
print('The object is writeable')
在这里,try子句只是访问属性write,而没有使用它来做任何事情。如果引发了AttributeError异常,说明对象没有属性write,否则就说明有这个属性。这种解决方案可替代7.2.9节介绍的使用getattr的解决方案,而且更自然。具体使用哪种解决方案,在很大程度上取决于个人喜好。请注意,这里在效率方面的提高并不大(实际上是微乎其微)。一般而言,除非程序存在性能方面的问题,否则不应过多考虑这样的优化。关键是在很多情况下,相比于使用if/else,使用try/except语句更自然,也更符合Python的风格。因此你应养成尽可能使用try/except语句的习惯
6.warning模块
如果你只想发出警告,指出情况偏离了正轨,可使用模块warnings中的函数warn。
from warnings import warn
warn('警告') # warn('警告')
print(123) #继续执行
警告只显示一次。如果再次运行最后一行代码,什么事情都不会发生。如果其他代码在使用你的模块,可使用模块warnings中的函数filterwarnings来抑制你发出的警告(或特定类型的警告),并指定要采取的措施,如”error”或”ignore”。
from warnings import filterwarnings
filterwarnings('警告') # warn('警告')
#filterwarnings ssert action in ("error", "ignore", "always", "default", "module",
from warnings import filterwarnings
filterwarnings("ignore")
warn("Anyone out there?")
filterwarnings("error")
warn("Something is very wrong!")
'''Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UserWarning: Something is very wrong!'''
如你所见,引发的异常为UserWarning。发出警告时,可指定将引发的异常(即警告类别),但必须是Warning的子类。如果将警告转换为错误,将使用你指定的异常。另外,还可根据异常来过滤掉特定类型的警告。
>>> filterwarnings("error")
>>> warn("This function is really old...", DeprecationWarning)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
DeprecationWarning: This function is really old...
>>> filterwarnings("ignore", category=DeprecationWarning)
>>> warn("Another deprecation warning.", DeprecationWarning)
>>> warn("Something else.")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UserWarning: Something else.
除上述基本用途外,模块warnings还提供了一些高级功能。如果你对此感兴趣,请参阅库参考手册。
7.小结
❑ 异常对象:异常情况(如发生错误)是用异常对象表示的。对于异常情况,有多种处理方式;如果忽略,将导致程序终止。
❑ 引发异常:可使用raise语句来引发异常。它将一个异常类或异常实例作为参数,但你也可提供两个参数(异常和错误消息)。如果在except子句中调用raise时没有提供任何参数,它将重新引发该子句捕获的异常。
❑ 自定义的异常类:你可通过从Exception派生来创建自定义的异常。
❑ 捕获异常:要捕获异常,可在try语句中使用except子句。在except子句中,如果没有指定异常类,将捕获所有的异常。你可指定多个异常类,方法是将它们放在元组中。如果向except提供两个参数,第二个参数将关联到异常对象。在同一条try/except语句中,可包含多个except子句,以便对不同的异常采取不同的措施。
❑ else子句:除except子句外,你还可使用else子句,它在主try块没有引发异常时执行。
❑ finally:要确保代码块(如清理代码)无论是否引发异常都将执行,可使用try/finally,并将代码块放在finally子句中。
❑ 异常和函数:在函数中引发异常时,异常将传播到调用函数的地方(对方法来说亦如此)。
❑ 警告:警告类似于异常,但(通常)只打印一条错误消息。你可指定警告类别,它们是Warning的子类。