文章大纲
程序运行时通常有两种情况:正常和异常。异常事件可能是错误也可能是其它没有预料的事件,Python 提供了异常处理机制,在面临异常时可以帮助处理,而不是通过条件语句一一的去处理,况且总有预料不到的错误。
异常
Python 使用异常对象来表示异常状态,当遇到错误时将引发异常。
当异常对象没有被处理(也称为捕获)时,程序会终止并显示错误的信息(traceback)
1 / 0
Traceback (most recent call last):
File "<pyshell#59>", line 1, in <module>
1 / 0
ZeroDivisionError: division by zero
异常对象都属于某个类,可以通过多种方式引发和捕获异常实例,从而采取措施,而不是让程序失败终止。
raise 语句
当出现问题是,会自动引发异常,也可以主动的引发异常。
使用 raise
语句,并将一个类(必须是 Exception
的子类)或实例作为参数。
将类作为参数时会自动创建一个实例。
raise Exception
Traceback (most recent call last):
File "<pyshell#62>", line 1, in <module>
raise Exception
Exception
可以在引发错误时,指出发生的信息:
raise Exception('hyperdrive overload')
Traceback (most recent call last):
File "<pyshell#64>", line 1, in <module>
raise Exception('hyperdrive overload')
Exception: hyperdrive overload
有很多内置的异常类,都可以用于 raise
语句中:
类名 | 描述 |
---|---|
Exception | 几乎所有的异常类都是基于它派生的 |
AttributeError | 引用属性或给它赋值失败时引发的 |
OSError | OS 不能执行指定的任务时引发,有多个子类 |
IndexError | 使用序列中不存在索引时引发,为 LookupError 的子类 |
KeyError | 使用隐射中不存在的键时引发,为 LookupError 的子类 |
NameError | 找不到名称时引发 |
SyntaxError | 代码不正确时引发 |
TypeError | 将内置操作或函数用于类型不正确的对象时引发 |
ValueError | 将内置操作或函数用于类型正确但包含的值不合适时引发 |
ZeroDivisionError | 在除法或求模运算的第二参数为零时引发 |
自定义异常类
虽然内置异常类涉及的范围很广,但可能需要实现自己的需求,也可以创建自定义异常类。
创建自定义异常类,与创建普通类是一样的,但是务必直接或间接的继承 Exception
类。
class CustomError(Exception): pass
捕获异常
异常是可以对其进行处理的,称为捕获异常。
使用 try/except
语句来捕获异常:
try:
1 / 0
except:
print("the second number can't be zero!")
the second number can't be zero!
重新引发
捕获异常后,如果要重新引发它(继续向上传播)可使用 raise
且不提供任何参数。
在设计程序时可以设置一个“抑制”开头,用来控制异常是否要继续向上传播。
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
calculator = MuffledCalculator()
calculator.calc('10/2')
5.0
calculator.calc('10/0')
Traceback (most recent call last):
File "<pyshell#90>", line 1, in <module>
calculator.calc('10/0')
File "<pyshell#87>", 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:
1 / 0
except ZeroDivisionError:
raise ValueError
Traceback (most recent call last):
File "<pyshell#100>", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<pyshell#100>", line 4, in <module>
raise ValueError
ValueError
可以使用 raise ... from ...
来提供自己的上下文,使用 None
可以禁用上下文:
try:
1 / 0
except ZeroDivisionError:
raise ValueError from None
Traceback (most recent call last):
File "<pyshell#102>", line 4, in <module>
raise ValueError from None
ValueError
多个 except 子句
在进行除法运算时,除数是零会引发异常,除数时非数值型也会产生异常,所以仅仅捕获 ZeroDivisionError
是不够的。可以在 try/except
语句中添加一个 except
子句:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
value = x / y
print('x / y is ', value)
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: '))
value = x / y
print('x / y is ', value)
except (ZeroDivisionError, TypeError):
print('Input a wrong number!')
捕获对象
可以在 except
子句中访问异常对象(实例)本身,如果是捕获多个异常(元组指定多个异常类型)时,也是返回一个对象。
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
value = x / y
print('x / y is ', value)
except (ZeroDivisionError, TypeError) as e:
print(e)
捕获所有异常
即使处理了好几种异常,可能还有一些没有预料到的异常,通常没有必要去隐藏没有被发现的异常,而是让程序立即崩溃,要捕获所有异常,只需在 except
语句中不指定任何异常类即可。
异常捕获中使用 else
可以像条件语句和循环一样,给捕获异常添加一个 else
子句:
try:
print('hello')
except:
print('what?')
else:
print('Ah..')
hello
Ah..
当出现异常时:
try:
1 / 0
print('hello')
except:
print('what?')
else:
print('Ah..')
what?
仅当没有引发异常时,才会执行 else
中的语句。
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 Exception as e:
print('Invalid input,', e)
print('pls try again.')
else:
break
当没有引发异常时将执行 else
语句中的 break
进而跳出循环。
finally 子句
finally
子句可用于在发生异常时执行清理工作,是 try
子句配套的命令。
x = None
try:
1 / 0
finally:
print('Cleaning up ...')
del x
使用 del
删除变量可能是不太适宜,但是 finally
非常适合用于确保文件或网络套接字等资源的关闭。
在一条语句中同时包含 try except finally else
:
try:
1 / 0
except NameError:
print('error value')
else:
print('well done')
finally:
print('Cleaning 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 "/usr/lib64/python3.12/idlelib/run.py", line 580, in runcode
exec(code, self.locals)
File "<pyshell#28>", line 1, in <module>
File "<pyshell#20>", line 2, in ignore_exception
File "<pyshell#14>", line 2, in faulty
Exception: Something is wrong
handle_exception()
Exception handled
最佳实践
可以使用条件语句来表达异常处理实现的目标,但这边编写出来的代码可能不那么自然,可读性没有那么高。
很多情况下,相比与使用 if/else
,使用 try/except
语句更加自然,也更符合 Python 的风格,尽量养成尽可能使用 try/except
语句的习惯。
不那么异常的情况
如果只想发出警号,可使用模块 warnings
中的 warn
函数。
from warnings import warn
warn('warning')
Warning (from warnings module):
File "<pyshell#34>", line 1
UserWarning: warning
warn('warning')
warn('important')
Warning (from warnings module):
File "<pyshell#36>", line 1
UserWarning: important
警告只会显示一次,如果再次执行最后一行,什么事情都不会发生。
可使用模块 warnings
中的函数 filterwarning
来抑制发出的警告(或特定类型的警告),并指定要采取的措施(error 或 ignore)。
from warnings import filterwarnings
filterwarnings("ignore")
warn('warning')
filterwarnings("error")
warn('warning')
Traceback (most recent call last):
File "<pyshell#44>", line 1, in <module>
warn('warning')
UserWarning: warning
还可以根据异常来过滤掉特定类型的警告:
filterwarnings("ignore",category=DeprecationWarning)
warn('This func is too old.', DeprecationWarning)
filterwarnings("error",category=DeprecationWarning)
warn('This func is too old.', DeprecationWarning)
Traceback (most recent call last):
File "<pyshell#54>", line 1, in <module>
warn('This func is too old.', DeprecationWarning)
DeprecationWarning: This func is too old.