文章目录
- 异常的种类
- 异常处理
- 触发异常
- 使用异常避免崩溃
- else 代码块
- 抛出异常
- 处理 ZeroDivisionError 异常
- 处理 FileNotFoundError 异常
- 断言
异常的种类
在python中不同的异常可以用不同的类型(python中统一了类与类型,类型即类)去标识,一个异常标识一种错误
常用异常
- AttributeError 试图访问一个对象没有的属性,比如foo.x,但是foo没有属性x
- IOError 输入/输出异常;基本上是无法打开文件
- ImportError 无法引入模块或包;基本上是路径问题或名称错误
- IndentationError 语法错误(的子类) ;代码没有正确对齐
- IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
- KeyError 试图访问字典里不存在的键
- KeyboardInterrupt Ctrl+C被按下
- NameError 使用一个还未被赋予对象的变量
- SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
- TypeError 传入对象类型与要求的不符合
- UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它
- ValueError 传入一个调用者不期望的值,即使值的类型是正确的
异常处理
如果错误发生的条件是可预知的,我们需要用if进行处理:在错误发生之前进行预防
AGE
=
10
while
True
:
age
=
input
(
'>>: '
)
.
strip
(
)
if
age
.
isdigit
(
)
:
#只有在age为字符串形式的整数时,下列代码才不会出错,该条件是可预知的
age
=
int
(
age
)
if
age
==
AGE
:
print
(
'you got it'
)
break
如果错误发生的条件是不可预知的,则需要用到
try...except
:在错误发生之后进行处理 使用 try-except 代码块
当你认为可能发生了错误时,可编写一个try-except代码块来处理可能引发的异常。你让Python尝试运行一些代码,并告诉它如果这些代码引发了指定的异常,该怎么办。
处理ZeroDivisionError异常的
try-except
代码块类似于下面这样:
try
:
print
(
5
/
0
)
except
ZeroDivisionError
:
print
(
"You can't divide by zero!"
)
将导致错误的代码行print(5/0)放在了一个try代码块中。如果try代码块中的代码运行起来没有问题,Python将跳过except代码块;如果try代码块中的代码导致了错误, Python将查找这样的except代码块,并运行其中的代码,即其中指定的错误与引发的错误相同。
try代码块中的代码引发了ZeroDivisionError异常,因此Python指出了该如何解决问题的except代码块,并运行其中的代码。这样,用户看到的是一条友好的错误消息,而不是traceback:
You can't divide by zero!
如果try-except代码块后面还有其他代码,程序将接着运行,因为已经告诉了Python如何处理这种错误。
- 异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。
s1
=
'hello'
try
:
int
(
s1
)
except
IndexError
as
e
:
# 未捕获到异常,程序直接报错
print
e
- 多分支
s1
=
'hello'
try
:
int
(
s1
)
except
IndexError
as
e
:
print
(
e
)
except
KeyError
as
e
:
print
(
e
)
except
ValueError
as
e
:
print
(
e
)
- 万能异常Exception
s1
=
'hello'
try
:
int
(
s1
)
except
Exception
as
e
:
print
(
e
)
#4 多分支异常与万能异常
#4.1 如果你想要的效果是,无论出现什么异常,我们统一丢弃,或者使用同一段代码逻辑去处理他们,那么骚年,大胆的去做吧,只有一个Exception就足够了。
#4.2 如果你想要的效果是,对于不同的异常我们需要定制不同的处理逻辑,那就需要用到多分支了。
#5 也可以在多分支后来一个Exception
s1
=
'hello'
try
:
int
(
s1
)
except
IndexError
as
e
:
print
(
e
)
except
KeyError
as
e
:
print
(
e
)
except
ValueError
as
e
:
print
(
e
)
except
Exception
as
e
:
print
(
e
)
- 异常的其他结构
s1
=
'hello'
try
:
int
(
s1
)
except
IndexError
as
e
:
print
(
e
)
except
KeyError
as
e
:
print
(
e
)
except
ValueError
as
e
:
print
(
e
)
#except Exception as e:
# print(e)
else
:
print
(
'try内代码块没有异常则执行我'
)
finally
:
print
(
'无论异常与否,都会执行该模块,通常是进行清理工作'
)
-
**
主动触发异常
**
try
:
raise
TypeError
(
'类型错误'
)
except
Exception
as
e
:
print
(
e
)
- 自定义异常
class
EgonException
(
BaseException
)
:
def
__init__
(
self
,
msg
)
:
self
.
msg
=
msg
def
__str__
(
self
)
:
return
self
.
msg
try
:
raise
EgonException
(
'类型错误'
)
except
EgonException
as
e
:
print
(
e
)
#9 断言:assert 条件
assert
1
==
1
assert
1
==
2
#10 总结try..except
1
:把错误处理和真正的工作分开来
2
:代码更易组织,更清晰,复杂的工作任务更容易实现;
3
:毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;
触发异常
我们可以使用raise语句自己触发异常
raise语法
格式如下:
raise [Exception [, args [, traceback]]]
语句中 Exception 是异常的类型(例如,NameError)参数标准异常中任一种,args 是自已提供的异常参数。
最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象。
一个异常可以是一个字符串,类或对象。 Python的内核提供的异常,大多数都是实例化的类,这是一个类的实例的参数。定义一个异常非常简单,如下所示:
def
functionName
(
level
)
:
if
level
<
1
:
raise
Exception
(
"Invalid level!"
,
level
)
# 触发异常后,后面的代码就不会再执行
注意:为了能够捕获异常,"except"语句必须有用相同的异常来抛出类对象或者字符串。
例如我们捕获以上异常,"except"语句如下所示:
try
:
正常逻辑
except
Exception
,
err
:
触发自定义异常
else
:
其余代码
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 定义函数
def
mye
(
level
)
:
if
level
<
1
:
raise
Exception
,
"Invalid level!"
# 触发异常后,后面的代码就不会再执行
try
:
mye
(
0
)
# 触发异常
except
Exception
as
err
:
print
1
,
err
else
:
print
2
#打印
1
Invalid level!
使用异常避免崩溃
发生错误时,如果程序还有工作没有完成,妥善地处理错误就尤其重要。这种情况经常会出现在要求用户提供输入的程序中;如果程序能够妥善地处理无效输入,就能再提示用户提供有效输入,而不至于崩溃。
print
(
"Give me two numbers, and I'll divide them."
)
print
(
"Enter 'q' to quit."
)
while
True
:
first_number
=
input
(
"\nFirst number: "
)
if
first_number
==
'q'
:
break
second_number
=
input
(
"Second number: "
)
if
second_number
==
'q'
:
break
answer
=
int
(
first_number
)
/
int
(
second_number
)
print
(
answer
)
这个程序提示用户输入一个数字,并将其存储到变量first_number中;如果用户输入的不是表示退出的q,就再提示用户输入一个数字,并将其存储到变量second_number中,接下来,我们计算这两个数字的商。这个程序没有采取任何处理错误的措施,因此让它执行除数为0的除法运算时,它将崩溃:
Give me two numbers, and I'll divide them.
Enter 'q' to quit.
First number: 5
Second number: 0
Traceback (most recent call last):
File "division.py", line 9, in
answer = int(first_number) / int(second_number)
ZeroDivisionError: division by zero
else 代码块
通过将可能引发错误的代码放在
try-except
代码块中,可提高这个程序抵御错误的能力。错误是执行除法运算的代码行导致的,因此我们需要将它放到
try-except
代码块中。这个示例还包含一个else代码块;依赖于try代码块成功执行的代码都应放到else代码块中:
print
(
"Give me two numbers, and I'll divide them."
)
print
(
"Enter 'q' to quit."
)
while
True
:
first_number
=
input
(
"\nFirst number: "
)
if
first_number
==
'q'
:
break
second_number
=
input
(
"Second number: "
)
try
:
answer
=
int
(
first_number
)
/
int
(
second_number
)
except
ZeroDivisionError
:
print
(
"You can't divide by 0!"
)
else
:
print
(
answer
)
让Python尝试执行try代码块中的除法运算,这个代码块只包含可能导致错误的代码。依赖于try代码块成功执行的代码都放在else代码块中;在这个示例中,如果除法运算成功,就使用else代码块来打印结果。
except代码块告诉Python,出现ZeroDivisionError异常时该怎么办。如果try代码块因除零错误而失败,我们就打印一条友好的消息,告诉用户如何避免这种错误。程序将继续运行,用户根本看不到traceback:
Give me two numbers, and I'll divide them.
Enter 'q' to quit.
First number: 5
Second number: 0
You can't divide by 0!
First number: 5
Second number: 2
2.5
First number: q
try-except-else代码块的工作原理大致如下: Python尝试执行try代码块中的代码;只有可能引发异常的代码才需要放在try语句中。有时候,有一些仅在try代码块成功执行时才需要运行的代码;这些代码应放在else代码块中。 except代码块告诉Python,如果它尝试运行try代码块中的代码时引发了指定的异常,该怎么办。
通过预测可能发生错误的代码,可编写健壮的程序,它们即便面临无效数据或缺少资源,也能继续运行,从而能够抵御无意的用户错误和恶意的攻击。
抛出异常
已看到如何使用 try 和 except 语句来处理 Python 的异常,这样程序就可以从你预期的异常中恢复。
但你也可以在代码中抛出自己的异常。抛出异常相当于是说:“停止运行这个函数中的代码,将程序执行转到 except 语句 ”
抛出异常使用 raise 语句。在代码中, raise 语句包含以下部分:
- raise 关键字;
- 对 Exception 函数的调用;
- 传递给 Exception 函数的字符串,包含有用的出错信息。
>>
>
raise
Exception
(
'This is the error message.'
)
Traceback
(
most recent call last
)
:
File
"
"
,
line
1
,
in
<
module
>
raise
Exception
(
'This is the error message.'
)
Exception
:
This
is
the error message
.
如果没有 try 和 except 语句覆盖抛出异常的 raise 语句,该程序就会崩溃,并显示异常的出错信息。
通常是调用该函数的代码知道如何处理异常,而不是该函数本身。所以你常常会看到 raise 语句在一个函数中, try 和 except 语句在调用该函数的代码中。 例如,打开一个新的文件编辑器窗口,输入以下代码,并保存为 boxPrint.py:
def
boxPrint
(
symbol
,
width
,
height
)
:
if
len
(
symbol
)
!=
1
:
raise
Exception
(
'Symbol must be a single character string.'
)
if
width
<=
2
:
raise
Exception
(
'Width must be greater than 2.'
)
if
height
<=
2
:
raise
Exception
(
'Height must be greater than 2.'
)
print
(
symbol
*
width
)
for
i
in
range
(
height
-
2
)
:
print
(
symbol
+
(
' '
*
(
width
-
2
)
)
+
symbol
)
print
(
symbol
*
width
)
for
sym
,
w
,
h
in
(
(
'*'
,
4
,
4
)
,
(
'O'
,
20
,
5
)
,
(
'x'
,
1
,
3
)
,
(
'ZZ'
,
3
,
3
)
)
:
try
:
boxPrint
(
sym
,
w
,
h
)
except
Exception
as
err
:
print
(
'An exception happened: '
+
str
(
err
)
)
处理 ZeroDivisionError 异常
下面来看一种导致Python引发异常的简单错误。你可能知道不能将一个数字除以0,但我们还是让Python这样做吧:
print
(
5
/
0
)
显然, Python无法这样做,因此你将看到一个traceback:
Traceback
(
most recent call last
)
:
File
"division.py"
,
line
1
,
in
<
module
>
print
(
5
/
0
)
ZeroDivisionError
:
division by zero
在上述traceback中, 错误
ZeroDivisionError
是一个异常对象。 Python无法按你的要求做时,就会创建这种对象。在这种情况下, Python将停止运行程序,并指出引发了哪种异常,而我们可根据这些信息对程序进行修改。下面我们将告诉Python,发生这种错误时怎么办;这样,如果再次发生这样的错误,我们就有备无患了。
处理 FileNotFoundError 异常
使用文件时,一种常见的问题是找不到文件:你要查找的文件可能在其他地方、文件名可能不正确或者这个文件根本就不存在。对于所有这些情形,都可使用try-except代码块以直观的方式进行处理。
尝试读取一个不存在的文件。下面的程序尝试读取文件alice.txt的内容,但我没有将这个文件存储在alice.py所在的目录中:
#alice.py
filename
=
'alice.txt'
with
open
(
filename
)
as
f_obj
:
contents
=
f_obj
.
read
(
)
Python无法读取不存在的文件,因此它引发一个异常:
Traceback
(
most recent call last
)
:
File
"alice.py"
,
line
3
,
in
<
module
>
with
open
(
filename
)
as
f_obj
:
FileNotFoundError
:
[
Errno
2
]
No such
file
or
directory
:
'alice.txt'
在上述traceback中,最后一行报告了FileNotFoundError异常,这是Python找不到要打开的文件时创建的异常。在这个示例中,这个
错误是函数open()导致的,
因此要处理这个错误,必须
将try语句放在包含open()的代码行之前
:
filename
=
'alice.txt'
try
:
with
open
(
filename
)
as
f_obj
:
contents
=
f_obj
.
read
(
)
except
FileNotFoundError
:
msg
=
"Sorry, the file "
+
filename
+
" does not exist."
print
(
msg
)
在这个示例中, try代码块引发FileNotFoundError异常,因此Python找出与该错误匹配的except代码块,并运行其中的代码。最终的结果是显示一条友好的错误消息,而不是traceback:
Sorry, the file alice.txt does not exist.
断言
“断言”是一个心智正常的检查,确保代码没有做什么明显错误的事情。这些心智正常的检查由
assert 语句
执行。如果检查失败,就会抛出异常。在代码中, assert语句包含以下部分:
- assert 关键字;
- 条件(即求值为 True 或 False 的表达式);
- 逗号;
- 当条件为 False 时显示的字符串。
>>
>
podBayDoorStatus
=
'open'
>>
>
assert
podBayDoorStatus
==
'open'
,
'The pod bay doors need to be "open".'
>>
>
podBayDoorStatus
=
'I\'m sorry, Dave. I\'m afraid I can'
t do that
.
''
>>
>
assert
podBayDoorStatus
==
'open'
,
'The pod bay doors need to be "open".'
Traceback
(
most recent call last
)
:
File
"
"
,
line
1
,
in
<
module
>
assert
podBayDoorStatus
==
'open'
,
'The pod bay doors need to be "open".'
AssertionError
:
The pod bay doors need to be
"open"
.
这里将 podBayDoorStatus 设置为 ‘open’,所以从此以后,我们充分期望这个变量的值是 ‘open’。在使用这个变量的程序中,基于这个值是 ‘open’ 的假定,我们可能写下了大量的代码,即这些代码依赖于它是 ‘open’,才能按照期望工作。所以添加了一个断言,确保假定 podBayDoorStatus 是 ‘open’ 是对的。这里,我们加入了信息 ‘Thepod bay doors need to be “open”.’,这样如果断言失败,就很容易看到哪里出了错。
稍后,假如我们犯了一个明显的错误,把另外的值赋给 podBayDoorStatus,但在很多行代码中,我们并没有意识到这一点。这个断言会抓住这个错误,清楚地告诉我们出了什么错。
在日常英语中, assert 语句是说:“我断言这个条件为真,如果不为真,程序中什么地方就有一个缺陷。”不像异常,代码不应该用 try 和 except 处理 assert 语句。如果assert 失败,程序就应该崩溃。通过这样的快速失败,产生缺陷和你第一次注意到该缺陷之间的时间就缩短了。这将减少为了寻找导致该缺陷的代码,而需要检查的代码量。
断言针对的是程序员的错误,而不是用户的错误。对于那些可以恢复的错误(诸如文件没有找到,或用户输入了无效的数据),请抛出异常,而不是用 assert 语句检测它。