提出例外情况
"请求宽恕比请求允许更容易"。- 格雷斯-霍珀
错误有两种形式:语法错误和异常。
语法错误发生在Python无法解析一行代码的时候,而引发异常则允许我们区分常规事件和特殊事件,如错误(如除以0)或你可能不期望处理的东西。使用条件语句来检查每一个可能的事件,不仅效率低下,缺乏灵活性,而且还影响了可读性。幸运的是,Python 提供了强大的异常处理机制来解决这个问题。
异常。异常对象代表特殊情况
- 当 Python 遇到一个错误时,它会引发一个异常。
- 如果异常对象没有被捕获,程序就会以回溯(错误信息)终止。
使用异常的好处是,不是只得到错误信息,而是可以捕获错误并做一些事情,而不是让整个程序失败。
raise 语句
使用raise
,其参数可以是一个类(子类Exception
)或一个实例。
使用一个类会自动创建一个实例。
>>> raise Exception('hyperdrive overload')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
Exception: hyperdrive overload
>>> raise ArithmeticError
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ArithmeticError
内置异常的例子
Exception
:基类AttributeError
:属性引用或赋值失败OSError
:操作系统不能执行一个任务(如:文件)IndexError
: 序列上的索引不存在(LookupError
的子类)KeyError
: 映射中的键不存在(LookupError
的子类)NameError
:未找到名称(变量)。SyntaxError
:代码中的语法错误TypeError
:内置操作或函数应用于错误类型的对象ValueError
:内置操作或函数应用于正确类型的对象,但有不适当的值ZeroDivisionError
:除法或模数操作的第二个参数是0
什么时候要创建自定义的异常类?
有时错误信息是不够的,所以你可以根据它们的类来选择性地处理某些类型的异常。
创建一个异常类涉及到子类Exception
或其任何一个子类的形式。
class NewCustomException(Exception): pass
捕获异常
可以使用try/except
语句来捕获异常。
在调用函数的地方没有捕获的异常将传播到程序的顶层。如果你已经调用了一个异常,但想再次引发它,你可以在没有任何参数的情况下调用raise
,或者明确地提供异常。
在与用户的交互式会话中,创建一个类是很有用的,而在程序的内部使用时,引发一个异常会更好。当引发一个不同的异常时,将你带入except
的异常将被作为上下文存储,并成为最终错误信息的一部分。
>>> try:
... 1/0
... except ZeroDivisionError:
... raise ValueError
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError
多于一个 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
子句只寻找ZeroDivisionError
,这个异常就溜走了,使程序崩溃了。
添加另一个异常可以解决这个问题。
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.")
在这里使用if
语句会更加困难,因为你必须定义什么样的值可以在除法中使用,而使用异常处理不会使代码变得杂乱无章,并且允许我们检查多种错误。
在一个块中指定一个以上的异常类型,可以用一个元组来完成。
except (ZeroDivisionError, TypeError, NameError):
可以捆绑这个由两个参数组成的异常对象。如果你想让程序继续运行,但为以后记录错误,这很有用。
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)
即使你处理几种类型的异常,你也不一定能预见到所有的异常。在我们的示例程序中,如果我们在提示符下按下回车键会发生什么?
该 堆栈跟踪(关于出错的信息)如下。
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: ''
选项。
- 立即崩溃程序,这样你就可以看到问题所在,而不是用
try/except
语句来隐藏异常,这样就不会捕获它了 - 在 except 子句中省略异常类
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except:
print('An error occurred')
这是有风险的,因为它同时隐藏了你预料到的和你没有预料到的错误。用户还必须用CTRL-C终止程序的执行,用sys.exit
终止函数。
有些情况下,使用except Exception as e
,并在对象上检查异常e
,以允许少数没有子类的异常溜过。
当事情进展顺利时
使用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
只有当没有异常发生时,循环才会被打破。只要有错误的事情发生,程序就会通过要求新的输入来运行。我们如何通过打印信息量更大的错误信息来捕获异常类的所有异常呢?
我们修改先前的代码。
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
,用于在可能发生的异常后做内务处理。这保证了finally
子句的执行,无论try
子句中出现什么异常,即关闭文件或网络套接字。
x = None
try:
x = 1/0
finally:
print('Cleaning up...')
del x
我们在try
子句之前初始化x,这样就可以在程序结束之前进行清理,而且如果我们把它放在try
子句中,它就不会因为ZeroDivisionError
而被赋值,所以你就不会发现这个错误。
警告
警告只显示一次,可以使用warn
模块中的filterwarnings
函数进行抑制或过滤。可能的操作包括 "错误 "和 "忽略"。你可以指定一个不同的警告类别,它是Warning
的一个子类。
from warnings import filterwarnings
filterwarnings("ignore")
warn("Anyone out there?")
filterwarnings("error")
warn("Something is wrong!")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UserWarning: Something is very wrong!