2024年最新【Python学习教程】Python异常处理机制_avoid division byzero,2024年最新一次违反常规的Python大厂面试经历
开发人员在编写程序时,难免会遇到错误,有的是编写人员疏忽造成的语法错误,有的是程序内部隐含逻辑问题造成的数据错误,还有的是程序运行时与系统的规则冲突造成的系统错误,等等。总的来说,编写程序时遇到的错误可大致分为 2 类,分别为语法错误和运行时错误。Python。
一、Python所有方向的学习路线
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、学习软件
工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。
三、入门学习视频
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
文章目录
- 什么是异常处理,Python常见异常类型(入门必读)
- Python异常处理机制到底有什么用?
- Python try except异常处理详解(入门必读)
- Python异常处理机制的底层实现
- Python try except else详解
- Python try except finally:资源回收
- Python异常处理机制结构详解
- Python logging模块用法快速攻略
- Python assert调试程序
转载于http://c.biancheng.net/python/
程序运行时常会碰到一些错误,例如除数为 0、年龄为负数、数组下标越界等,这些错误如果不能发现并加以处理,很可能会导致程序崩溃。
和 C++、Java 这些编程语言一样,Python 也提供了处理异常的机制,可以让我们捕获并处理这些错误,让程序继续沿着一条不会出错的路径执行。
可以简单的理解异常处理机制,就是在程序运行出现错误时,让 Python 解释器执行事先准备好的除错程序,进而尝试恢复程序的执行。
借助异常处理机制,甚至在程序崩溃前也可以做一些必要的工作,例如将内存中的数据写入文件、关闭打开的文件、释放分配的内存等。
Python 异常处理机制会涉及 try、except、else、finally 这 4 个关键字,同时还提供了可主动使程序引发异常的 raise 语句,本章都会为你一一讲解。
什么是异常处理,Python常见异常类型(入门必读)
开发人员在编写程序时,难免会遇到错误,有的是编写人员疏忽造成的语法错误,有的是程序内部隐含逻辑问题造成的数据错误,还有的是程序运行时与系统的规则冲突造成的系统错误,等等。
总的来说,编写程序时遇到的错误可大致分为 2 类,分别为语法错误和运行时错误。
Python语法错误
语法错误,也就是解析代码时出现的错误。当代码不符合 Python 语法规则时,Python解释器在解析时就会报出 SyntaxError 语法错误,与此同时还会明确指出最早探测到错误的语句。例如:
print “Hello,World!”
我们知道,Python 3 已不再支持上面这种写法,所以在运行时,解释器会报如下错误:
SyntaxError: Missing parentheses in call to ‘print’
语法错误多是开发者疏忽导致的,属于真正意义上的错误,是解释器无法容忍的,因此,只有将程序中的所有语法错误全部纠正,程序才能执行。
Python运行时错误
运行时错误,即程序在语法上都是正确的,但在运行时发生了错误。例如:
a = 1/0
上面这句代码的意思是“用 1 除以 0,并赋值给 a 。因为 0 作除数是没有意义的,所以运行后会产生如下错误:
a = 1/0
Traceback (most recent call last):
File “<pyshell#2>”, line 1, in
a = 1/0
ZeroDivisionError: division by zero
以上运行输出结果中,前两段指明了错误的位置,最后一句表示出错的类型。在 Python 中,把这种运行时产生错误的情况叫做异常(Exceptions)。这种异常情况还有很多,常见的几种异常情况如表 1 所示。
异常类型 | 含义 | 实例 |
---|---|---|
AssertionError | 当 assert 关键字后的条件为假时,程序运行会停止并抛出 AssertionError 异常 | >>> demo_list = [‘C语言中文网’] >>> assert len(demo_list) > 0 >>> demo_list.pop() ‘C语言中文网’ >>> assert len(demo_list) > 0 Traceback (most recent call last): File “<pyshell#6>”, line 1, in assert len(demo_list) > 0 AssertionError |
AttributeError | 当试图访问的对象属性不存在时抛出的异常 | >>> demo_list = [‘C语言中文网’] >>> demo_list.len Traceback (most recent call last): File “<pyshell#10>”, line 1, in demo_list.len AttributeError: ‘list’ object has no attribute ‘len’ |
IndexError | 索引超出序列范围会引发此异常 | >>> demo_list = [‘C语言中文网’] >>> demo_list[3] Traceback (most recent call last): File “<pyshell#8>”, line 1, in demo_list[3] IndexError: list index out of range |
KeyError | 字典中查找一个不存在的关键字时引发此异常 | >>> demo_dict={‘C语言中文网’:“c.biancheng.net”} >>> demo_dict[“C语言”] Traceback (most recent call last): File “<pyshell#12>”, line 1, in demo_dict[“C语言”] KeyError: ‘C语言’ |
NameError | 尝试访问一个未声明的变量时,引发此异常 | >>> C语言中文网 Traceback (most recent call last): File “<pyshell#15>”, line 1, in C语言中文网 NameError: name ‘C语言中文网’ is not defined |
TypeError | 不同类型数据之间的无效操作 | >>> 1+‘C语言中文网’ Traceback (most recent call last): File “<pyshell#17>”, line 1, in 1+‘C语言中文网’ TypeError: unsupported operand type(s) for +: ‘int’ and ‘str’ |
ZeroDivisionError | 除法运算中除数为 0 引发此异常 | >>> a = 1/0 Traceback (most recent call last): File “<pyshell#2>”, line 1, in a = 1/0 ZeroDivisionError: division by zero |
提示:表中的异常类型不需要记住,只需简单了解即可。
当一个程序发生异常时,代表该程序在执行时出现了非正常的情况,无法再执行下去。默认情况下,程序是要终止的。如果要避免程序退出,可以使用捕获异常的方式获取这个异常的名称,再通过其他的逻辑代码让程序继续运行,这种根据异常做出的逻辑处理叫作异常处理。
开发者可以使用异常处理全面地控制自己的程序。异常处理不仅仅能够管理正常的流程运行,还能够在程序出错时对程序进行必的处理。大大提高了程序的健壮性和人机交互的友好性。
那么,应该如何捕获和处理异常呢?可以使用 try 语句来实现。有关 try 语句的语法和用法,会在后续章节继续详解。
Python异常处理机制到底有什么用?
异常处理是现代编程语言不可或缺的能力,它已经成为衡量一门编程语言是否成熟和健壮的标准之一,C++、Java、C#、Python 等高级语言都提供了异常处理机制。
无论你是多么优秀的程序员,你都不能保证自己的程序永远不会出错。就算你的程序没有错,用户也不一定按照你设定的规则来使用你的程序,总有一些小白或者极客会“玩弄”你的程序。
除此以外,你也不能保证程序的运行环境永远稳定,比如操作系统可能崩溃,网络可能无法连接,内存可能突然坏掉……
总之,你基本什么都保证不了。但是,作为一个负责任的程序员,我们要让自己的程序尽可能的健壮,尽可能保证在恶劣环境下还能正常运行,或者给用户提示错误,让用户决定是否退出。
例如有一个五子棋程序,当用户输入落子的坐标时,程序既要判断输入格式是否正确(横坐标和纵坐标之间由逗号分隔),还要判断坐标是否在合法的范围内。一般我们都会这样来处理:
if 坐标包含了除逗号之外的其它非数字字符:
alert 坐标只能是数值
goto retry
elif 坐标不包含逗号:
alert 必须使用逗号分隔横坐标和纵坐标
goto retry
elif 坐标落在了棋盘外:
alert 坐标必须位于棋盘之内
goto retry
elif 作为位置已有其它棋子:
alert 只能在没有棋子的位置落子
goto retry
else:
#正常的业务代码
…
上面的代码并没有涉及所有出错情形,只是考虑了四种可能出错的情形,代码量就已经急剧增加了。
在实际开发中,不可预料的情况呈数量级增长,甚至不能穷举,按照上面的逻辑来处理各种错误简直让人抓狂。
如果每次在实现真正的业务逻辑之前,都需要不厌其烦地考虑各种可能出错的情况,针对各种错误情况给出补救措施,这是多么乏味的事情啊。程序员喜欢解决问题,喜欢开发带来的“创造”快感,但不喜欢像一个“堵漏”工人,去堵那些由外在条件造成的“漏洞”。
对于构造大型、健壮、可维护的应用而言,错误处理是整个应用需要考虑的重要方面,程序员不能仅仅只做“对”的事情,程序员开发程序的过程,是一个创造的过程,这个过程需要有全面的考虑,仅做“对”的事情是远远不够的。
对于上面的错误处理机制,主要有如下两个缺点:
- 无法穷举所有的异常情况。因为人类知识的限制,异常情况总比可以考虑到的情况多,总有“漏网之鱼”的异常情况,所以程序总是不够健壮。
- 错误处理代码和业务实现代码混杂。这种错误处理和业务实现混杂的代码严重影响程序的可读性,会增加程序维护的难度。
程序员希望有一种强大的机制来解决上面的问题,能够将上面程序改成如下的形式:
if 用户输入不合法:
alert 输入不合法
goto retry
else :
#正常的业务代码
…
上面伪码提供了一个非常强大的“if 块”,即程序不管输入错误的原因是什么,只要用户输入不满足要求,程序就一次处理所有的错误。这种处理方法的好处是,使得错误处理代码变得更有条理,只需在一个地方处理错误。
现在的问题是,“用户输入不合法”这个条件怎么定义?当然,对于这个简单的要求,可以使用正则表达式对用户输入进行匹配,当用户输入与正则表达式不匹配时即可判断“用户输入不合法”。但对于更复杂的情形,就没有这么简单了。使用 Python 的异常处理机制就可以解决这个问题,例如:
try:
if(用户输入不合理):
raise 异常
except Exception:
alert 输入不合法
goto retry
#正常的业务代码
此程序中,通过在 try 块中判断用户的输入数据是否合理,如果不合理,程序受 raise 的影响会进行到 except 代码块,对用户的错误输出进行处理,然后会继续执行正常的业务代码;反之,如果用户输入合理,那么程序将直接执行正常的业务代码。
try except 是 Python 实现异常处理机制的核心结构,其具体用法会在后续章节做详细介绍。
显然,使用 Python 异常处理机制,可以让程序中的异常处理代码和正常业务代码分离,使得程序代码更加优雅,并可以提高程序的健壮性。
Python try except异常处理详解(入门必读)
Python 中,用try except
语句块捕获并处理异常,其基本语法结构如下所示:
try:
可能产生异常的代码块
except [ (Error1, Error2, … ) [as e] ]:
处理异常的代码块1
except [ (Error3, Error4, … ) [as e] ]:
处理异常的代码块2
except [Exception]:
处理其它异常
该格式中,[] 括起来的部分可以使用,也可以省略。其中各部分的含义如下:
- (Error1, Error2,…) 、(Error3, Error4,…):其中,Error1、Error2、Error3 和 Error4 都是具体的异常类型。显然,一个 except 块可以同时处理多种异常。
- [as e]:作为可选参数,表示给异常类型起一个别名 e,这样做的好处是方便在 except 块中调用异常类型(后续会用到)。
- [Exception]:作为可选参数,可以代指程序可能发生的所有异常情况,其通常用在最后一个 except 块。
从try except
的基本语法格式可以看出,try 块有且仅有一个,但 except 代码块可以有多个,且每个 except 块都可以同时处理多种异常。
当程序发生不同的意外情况时,会对应特定的异常类型,Python 解释器会根据该异常类型选择对应的 except 块来处理该异常。
try except 语句的执行流程如下:
- 首先执行 try 中的代码块,如果执行过程中出现异常,系统会自动生成一个异常类型,并将该异常提交给 Python 解释器,此过程称为捕获异常。
- 当 Python 解释器收到异常对象时,会寻找能处理该异常对象的 except 块,如果找到合适的 except 块,则把该异常对象交给该 except 块处理,这个过程被称为处理异常。如果 Python 解释器找不到处理异常的 except 块,则程序运行终止,Python 解释器也将退出。
事实上,不管程序代码块是否处于 try 块中,甚至包括 except 块中的代码,只要执行该代码块时出现了异常,系统都会自动生成对应类型的异常。但是,如果此段程序没有用 try 包裹,又或者没有为该异常配置处理它的 except 块,则 Python 解释器将无法处理,程序就会停止运行;反之,如果程序发生的异常经 try 捕获并由 except 处理完成,则程序可以继续执行。
举个例子:
try:
a = int(input("输入被除数:"))
b = int(input("输入除数:"))
c = a / b
print("您输入的两个数相除的结果是:", c )
except (ValueError, ArithmeticError):
print("程序发生了数字格式异常、算术异常之一")
except :
print("未知异常")
print("程序继续运行")
程序运行结果为:
输入被除数:a
程序发生了数字格式异常、算术异常之一
程序继续运行
上面程序中,第 6 行代码使用了(ValueError, ArithmeticError)来指定所捕获的异常类型,这就表明该 except 块可以同时捕获这 2 种类型的异常;第 8 行代码只有 except 关键字,并未指定具体要捕获的异常类型,这种省略异常类的 except 语句也是合法的,它表示可捕获所有类型的异常,一般会作为异常捕获的最后一个 except 块。
除此之外,由于 try 块中引发了异常,并被 except 块成功捕获,因此程序才可以继续执行,才有了“程序继续运行”的输出结果。
获取特定异常的有关信息
通过前面的学习,我们已经可以捕获程序中可能发生的异常,并对其进行处理。但是,由于一个 except 可以同时处理多个异常,那么我们如何知道当前处理的到底是哪种异常呢?
其实,每种异常类型都提供了如下几个属性和方法,通过调用它们,就可以获取当前处理异常类型的相关信息:
- args:返回异常的错误编号和描述字符串;
- str(e):返回异常信息,但不包括异常信息的类型;
- repr(e):返回较全的异常信息,包括异常信息的类型。
举个例子:
try:
1/0
except Exception as e:
# 访问异常的错误编号和详细信息
print(e.args)
print(str(e))
print(repr(e))
输出结果为:
(‘division by zero’,)
division by zero
ZeroDivisionError(‘division by zero’,)
除此之外,如果想要更加详细的异常信息,可以使用 traceback 模块。有兴趣的读者,可自行查阅资料学习。
从程序中可以看到,由于 except 可能接收多种异常,因此为了操作方便,可以直接给每一个进入到此 except 块的异常,起一个统一的别名 e。
在 Python 2.x 的早期版本中,除了使用 as e 这个格式,还可以将其中的 as 用逗号(,)代替。
Python异常处理机制的底层实现
前面章节中,我们详细介绍了try except
异常处理的用法,简单来说,当位于 try 块中的程序执行出现异常时,会将该种异常捕获,同时找到对应的 except 块处理该异常,那么这里就有一个问题,它是如何找到对应的 except 块的呢?
我们知道,一个 try 块也可以对应多个 except 块,一个 except 块可以同时处理多种异常。如果我们想使用一个 except 块处理所有异常,就可以这样写:
try:
#...
except Exception:
#...
这种情况下,对于 try 块中可能出现的任何异常,Python 解释器都会交给仅有的这个 except 块处理,因为它的参数是 Exception,表示可以接收任何类型的异常。
注意,对于可以接收任何异常的 except 来说,其后可以跟 Exception,也可以不跟任何参数,但表示的含义都是一样的。
这里就要详细介绍一下 Exception。要知道,为了表示程序中可能出现的各种异常,Python 提供了大量的异常类,这些异常类之间有严格的继承关系,图 1 显示了 Python 的常见异常类之间的继承关系。
图 1 Python 的常见异常类之间的继承关系
从图 1 中可以看出,BaseException 是 Python 中所有异常类的基类,但对于我们来说,最主要的是 Exception 类,因为程序中可能出现的各种异常,都继承自 Exception。
因此,如果用户要实现自定义异常,不应该继承 BaseException ,而应该继承 Exception 类。关于如何自定义一个异常类,可阅读《Python自定义异常类》一节。
当 try 块捕获到异常对象后,Python 解释器会拿这个异常类型依次和各个 except 块指定的异常类进行比较,如果捕获到的这个异常类,和某个 except 块后的异常类一样,又或者是该异常类的子类,那么 Python 解释器就会调用这个 except 块来处理异常;反之,Python 解释器会继续比较,直到和最后一个 except 比较完,如果没有比对成功,则证明该异常无法处理。
图 2 演示了位于 try 块中的程序发生异常时,从捕获异常到处理异常的整个流程。
图 2 Python 异常捕获流程示意图
下面看几个简单的异常捕获的例子:
try:
a = int(input("输入 a:"))
b = int(input("输入 b:"))
print( a/b )
except ValueError:
print("数值错误:程序只能接收整数参数")
except ArithmeticError:
print("算术错误")
except Exception:
print("未知异常")
该程序中,根据用户输入 a 和 b 值的不同,可能会导致 ValueError、ArithmeticError 异常:
- 如果用户输入的 a 或者 b 是其他字符,而不是数字,会发生 ValueError 异常,try 块会捕获到该类型异常,同时 Python 解释器会调用第一个 except 块处理异常;
- 如果用户输入的 a 和 b 是数字,但 b 的值为 0,由于在进行除法运算时除数不能为 0,因此会发生 ArithmeticError 异常,try 块会捕获该异常,同时 Python 解释器会调用第二个 except 块处理异常;
- 当然,程序运行过程中,还可能由于其他因素出现异常,try 块都可以捕获,同时 Python 会调用最后一个 except 块来处理。
当一个 try 块配有多个 except 块时,这些 except 块应遵循这样一个排序规则,即可处理全部异常的 except 块(参数为 Exception,也可以什么都不写)要放到所有 except 块的后面,且所有父类异常的 except 块要放到子类异常的 except 块的后面。
Python try except else详解
在原本的try except
结构的基础上,Python 异常处理机制还提供了一个 else 块,也就是原有 try except 语句的基础上再添加一个 else 块,即try except else
结构。
使用 else 包裹的代码,只有当 try 块没有捕获到任何异常时,才会得到执行;反之,如果 try 块捕获到异常,即便调用对应的 except 处理完异常,else 块中的代码也不会得到执行。
举个例子:
try:
result = 20 / int(input('请输入除数:'))
print(result)
except ValueError:
print('必须输入整数')
except ArithmeticError:
print('算术错误,除数不能为 0')
else:
print('没有出现异常')
print("继续执行")
可以看到,在原有 try except 的基础上,我们为其添加了 else 块。现在执行该程序:
请输入除数:4
5.0
没有出现异常
继续执行
如上所示,当我们输入正确的数据时,try 块中的程序正常执行,Python 解释器执行完 try 块中的程序之后,会继续执行 else 块中的程序,继而执行后续的程序。
读者可能会问,既然 Python 解释器按照顺序执行代码,那么 else 块有什么存在的必要呢?直接将 else 块中的代码编写在 try except 块的后面,不是一样吗?
当然不一样,现在再次执行上面的代码:
请输入除数:a
必须输入整数
继续执行
可以看到,当我们试图进行非法输入时,程序会发生异常并被 try 捕获,Python 解释器会调用相应的 except 块处理该异常。但是异常处理完毕之后,Python 解释器并没有接着执行 else 块中的代码,而是跳过 else,去执行后续的代码。
也就是说,else 的功能,只有当 try 块捕获到异常时才能显现出来。在这种情况下,else 块中的代码不会得到执行的机会。而如果我们直接把 else 块去掉,将其中的代码编写到 try except 的后面:
try:
result = 20 / int(input('请输入除数:'))
print(result)
except ValueError:
print('必须输入整数')
except ArithmeticError:
print('算术错误,除数不能为 0')
print('没有出现异常')
print("继续执行")
程序执行结果为:
请输入除数:a
必须输入整数
没有出现异常
继续执行
可以看到,如果不使用 else 块,try 块捕获到异常并通过 except 成功处理,后续所有程序都会依次被执行。
Python try except finally:资源回收
Python 异常处理机制还提供了一个 finally 语句,通常用来为 try 块中的程序做扫尾清理工作。
注意,和 else 语句不同,finally 只要求和 try 搭配使用,而至于该结构中是否包含 except 以及 else,对于 finally 不是必须的(else 必须和 try except 搭配使用)。
在整个异常处理机制中,finally 语句的功能是:无论 try 块是否发生异常,最终都要进入 finally 语句,并执行其中的代码块。
基于 finally 语句的这种特性,在某些情况下,当 try 块中的程序打开了一些物理资源(文件、数据库连接等)时,由于这些资源必须手动回收,而回收工作通常就放在 finally 块中。
Python 垃圾回收机制,只能帮我们回收变量、类对象占用的内存,而无法自动完成类似关闭文件、数据库连接等这些的工作。
读者可能会问,回收这些物理资源,必须使用 finally 块吗?当然不是,但使用 finally 块是比较好的选择。首先,try 块不适合做资源回收工作,因为一旦 try 块中的某行代码发生异常,则其后续的代码将不会得到执行;其次 except 和 else 也不适合,它们都可能不会得到执行。而 finally 块中的代码,无论 try 块是否发生异常,该块中的代码都会被执行。
举个例子:
try:
a = int(input("请输入 a 的值:"))
print(20/a)
except:
print("发生异常!")
else:
print("执行 else 块中的代码")
finally :
print("执行 finally 块中的代码")
运行此程序:
(1)Python所有方向的学习路线(新版)
这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
最近我才对这些路线做了一下新的更新,知识体系更全面了。
(2)Python学习视频
包含了Python入门、爬虫、数据分析和web开发的学习视频,总共100多个,虽然没有那么全面,但是对于入门来说是没问题的,学完这些之后,你可以按照我上面的学习路线去网上找其他的知识资源进行进阶。
(3)100多个练手项目
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了,只是里面的项目比较多,水平也是参差不齐,大家可以挑自己能做的项目去练练。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
更多推荐
所有评论(0)