【Python学习教程】Python异常处理机制_avoid division byzero

我们知道,一个 try 块也可以对应多个 except 块,一个 except 块可以同时处理多种异常。如果我们想使用一个 except 块处理所有异常,就可以这样写:

try:
    #...
except Exception:
    #...

这种情况下,对于 try 块中可能出现的任何异常,Python 解释器都会交给仅有的这个 except 块处理,因为它的参数是 Exception,表示可以接收任何类型的异常。

注意,对于可以接收任何异常的 except 来说,其后可以跟 Exception,也可以不跟任何参数,但表示的含义都是一样的。

这里就要详细介绍一下 Exception。要知道,为了表示程序中可能出现的各种异常,Python 提供了大量的异常类,这些异常类之间有严格的继承关系,图 1 显示了 Python 的常见异常类之间的继承关系。

Python 的常见异常类之间的继承关系
图 1 Python 的常见异常类之间的继承关系

从图 1 中可以看出,BaseException 是 Python 中所有异常类的基类,但对于我们来说,最主要的是 Exception 类,因为程序中可能出现的各种异常,都继承自 Exception。

因此,如果用户要实现自定义异常,不应该继承 BaseException ,而应该继承 Exception 类。关于如何自定义一个异常类,可阅读《Python自定义异常类》一节。

当 try 块捕获到异常对象后,Python 解释器会拿这个异常类型依次和各个 except 块指定的异常类进行比较,如果捕获到的这个异常类,和某个 except 块后的异常类一样,又或者是该异常类的子类,那么 Python 解释器就会调用这个 except 块来处理异常;反之,Python 解释器会继续比较,直到和最后一个 except 比较完,如果没有比对成功,则证明该异常无法处理。

图 2 演示了位于 try 块中的程序发生异常时,从捕获异常到处理异常的整个流程。

Python 异常捕获流程示意图
图 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 异常:

  1. 如果用户输入的 a 或者 b 是其他字符,而不是数字,会发生 ValueError 异常,try 块会捕获到该类型异常,同时 Python 解释器会调用第一个 except 块处理异常;
  2. 如果用户输入的 a 和 b 是数字,但 b 的值为 0,由于在进行除法运算时除数不能为 0,因此会发生 ArithmeticError 异常,try 块会捕获该异常,同时 Python 解释器会调用第二个 except 块处理异常;
  3. 当然,程序运行过程中,还可能由于其他因素出现异常,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 块中的代码")

运行此程序:

请输入 a 的值:4
5.0
执行 else 块中的代码
执行 finally 块中的代码

可以看到,当 try 块中代码为发生异常时,except 块不会执行,else 块和 finally 块中的代码会被执行。

再次运行程序:

请输入 a 的值:a
发生异常!
执行 finally 块中的代码

可以看到,当 try 块中代码发生异常时,except 块得到执行,而 else 块中的代码将不执行,finally 块中的代码仍然会被执行。

finally 块的强大还远不止此,即便当 try 块发生异常,且没有合适和 except 处理异常时,finally 块中的代码也会得到执行。例如:

try:
    #发生异常
    print(20/0)
finally :
    print("执行 finally 块中的代码")

程序执行结果为:

执行 finally 块中的代码
Traceback (most recent call last):
File “D:\python3.6\1.py”, line 3, in
print(20/0)
ZeroDivisionError: division by zero

可以看到,当 try 块中代码发生异常,导致程序崩溃时,在崩溃前 Python 解释器也会执行 finally 块中的代码。

Python异常处理机制结构详解

到本节为止,读者已经学习了整个 Python 的异常处理机制的结构,接下来带领大家回顾一下,在此过程还会讲解一些新的知识。

首先,Python 完整的异常处理语法结构如下:

try:
#业务实现代码
except Exception1 as e:
#异常处理块1

except Exception2 as e:
#异常处理块2

#可以有多个 except

else:
#正常处理块
finally :
#资源回收块

整个异常处理结构的执行过程,如图 1 所示。

img
图 1 异常处理语句块的执行流程

注意,在整个异常处理结构中,只有 try 块是必需的,也就是说:

  • 如果没有 try 块,则不能有后面的 except 块、else 块和 finally 块。但是也不能只使用 try 块,要么使用 try except 结构,要么使用 try finally 结构;
  • except 块、else 块、finally 块都是可选的,当然也可以同时出现;
  • 可以有多个 except 块,但捕获父类异常的 except 块应该位于捕获子类异常的 except 块的后面;
  • 多个 except 块必须位于 try 块之后,finally 块必须位于所有的 except 块之后。
  • 要使用 else 块,其前面必须包含 try 和 except。

其中,很多初学者分不清 finally 和 else 的区别,这里着重说一下。else 语句块只有在没有异常发生的情况下才会执行,而 finally 语句则不管异常是否发生都会执行。不仅如此,无论是正常退出、遇到异常退出,还是通过 break、continue、return 语句退出,finally 语句块都会执行。

注意,如果程序中运行了强制退出 Python 解释器的语句(如 os._exit(1) ),则 finally 语句将无法得到执行。例如:

import os
try:
    os._exit(1)
finally:
    print("执行finally语句")

运行程序,没有任何输出。因此,除非在 try 块、except 块中调用了退出 Python 解释器的方法,否则不管在 try 块、except 块中执行怎样的代码,出现怎样的情况,异常处理的 finally 块总会被执行。

另外在通常情况下,不要在 finally 块中使用如 return 或 raise 等导致方法中止的语句(raise 语句将在后面介绍),一旦在 finally 块中使用了 return 或 raise 语句,将会导致 try 块、except 块中的 return、raise 语句失效。看如下程序:

def test():
    try:
        # 因为finally块中包含了return语句
        # 所以下面的return语句失去作用
        return True
    finally:
        return False
print(test())

上面程序在 finally 块中定义了一条 return False 语句,这将导致 try 块中的 return true 失去作用。运行上面程序,输出结果为:

False

同样,如果 Python 程序在执行 try 块、except 块包含有 return 或 raise 语句,则 Python 解释器执行到该语句时,会先去查找 finally 块,如果没有 finally 块,程序才会立即执行 return 或 raise 语句;反之,如果找到 finally 块,系统立即开始执行 finally 块,只有当 finally 块执行完成后,系统才会再次跳回来执行 try 块、except 块里的 return 或 raise 语句。

但是,如果在 finally 块里也使用了 return 或 raise 等导致方法中止的语句,finally 块己经中止了方法,系统将不会跳回去执行 try 块、except 块里的任何代码。

尽量避免在 finally 块里使用 return 或 raise 等导致方法中止的语句,否则可能出现一些很奇怪的情况。

Python logging模块用法快速攻略

无论使用哪种编程语言,最常用的调试代码的方式是:使用输出语句(比如 C 语言中使用 printf,Python 中使用 print() 函数)输出程序运行过程中一些关键的变量的值,查看它们的值是否正确,从而找到出错的地方。这种调试方法最大的缺点是,当找到问题所在之后,需要再将用于调试的输出语句删掉。

在 Python 中,有一种比频繁使用 print() 调试程序更简便的方法,就是使用 logging 模块,该模块可以很容易地创建自定义的消息记录,这些日志消息将描述程序执行何时到达日志函数调用,并列出指定的任何变量当时的值。

启用 logging 模块很简单,直接将下面的代码复制到程序开头:

import logging
logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')

读者不需要关心这两行代码的具体工作原理,但基本上,当 Python 记录一个事件的日志时,它会创建一个 LogRecord 对象,保存关于该事件的信息。

假如我们编写了如下一个函数,其设计的初衷是用来计算一个数的阶乘,但该函数有些问题,需要调试:

import logging
logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')
logging.debug('Start of program')
def factorial(n):
    logging.debug('Start of factorial(%s%%)' % (n))
    total = 1
    for i in range(n + 1):
        total *= i
        logging.debug('i is ' + str(i) + ', total is ' + str(total))
    logging.debug('End of factorial(%s%%)' % (n))
    return total
print(factorial(5))
logging.debug('End of program')

运行结果为:

2019-09-11 14:14:56,928 - DEBUG - Start of program
2019-09-11 14:14:56,945 - DEBUG - Start of factorial(5%)
2019-09-11 14:14:56,959 - DEBUG - i is 0, total is 0
2019-09-11 14:14:56,967 - DEBUG - i is 1, total is 0
2019-09-11 14:14:56,979 - DEBUG - i is 2, total is 0
2019-09-11 14:14:56,991 - DEBUG - i is 3, total is 0
2019-09-11 14:14:57,000 - DEBUG - i is 4, total is 0
2019-09-11 14:14:57,013 - DEBUG - i is 5, total is 0
2019-09-11 14:14:57,024 - DEBUG - End of factorial(5%)
0
2019-09-11 14:14:57,042 - DEBUG - End of program

可以看到,通过 logging.debug() 函数可以打印日志信息,这个 debug() 函数将调用 basicConfig() 打印一行信息,这行信息的格式是在 basicConfig() 函数中指定的,并且包括传递给 debug() 的消息。

分析程序的运行结果,factorial(5) 返回 0 作为 5 的阶乘的结果,这显然是不对的。for 循环应该用从 1 到 5 的数,乘以 total 的值,但 logging.debug() 显示的日志信息表明,i 变量从 0 开始,而不是 1。因为 0 乘任何数都是 0,所以接下来的迭代中,total 的值都是错的。日志消息提供了可以追踪的痕迹,帮助我们弄清楚程序运行过程哪里不对。

将代码行 for i in range(n + 1):改为 for i in range(1,n + 1):,再次运行程序,输出结果为:

2019-09-11 14:21:18,047 - DEBUG - Start of program
2019-09-11 14:21:18,067 - DEBUG - Start of factorial(5%)
2019-09-11 14:21:18,072 - DEBUG - i is 1, total is 1
2019-09-11 14:21:18,082 - DEBUG - i is 2, total is 2
2019-09-11 14:21:18,087 - DEBUG - i is 3, total is 6
2019-09-11 14:21:18,093 - DEBUG - i is 4, total is 24
2019-09-11 14:21:18,101 - DEBUG - i is 5, total is 120
2019-09-11 14:21:18,106 - DEBUG - End of factorial(5%)
120
2019-09-11 14:21:18,123 - DEBUG - End of program

Python logging日志级别

“日志级别”提供了一种方式,按重要性对日志消息进行分类。5 个日志级别如表 1 所示,从最不重要到最重要。利用不同的日志函数,消息可以按某个级别记入日志。

级别对应的函数描述
DEBUGlogging.debug()最低级别,用于小细节,通常只有在诊断问题时,才会关心这些消息。
INFOlogging.info()用于记录程序中一般事件的信息,或确认一切工作正常。
WARNINGlogging.warning()用于表示可能的问题,它不会阻止程序的工作,但将来可能会。
ERRORlogging.error()用于记录错误,它导致程序做某事失败。
CRITICALlogging.critical()最高级别,用于表示致命的错误,它导致或将要导致程序完全停止工作。

日志消息将会作为一个字符串,传递给这些函数。另外,日志级别只是一种建议,归根到底还是由程序员自己来决定日志消息属于哪一种类型。

举个例子:

import logging
logging.basicConfig(level=logging.DEBUG, format=’ %(asctime)s - %(levelname)s - %(message)s’)
logging.debug(‘Some debugging details.’)
2019-09-11 14:32:34,249 - DEBUG - Some debugging details.
logging.info(‘The logging module is working.’)
2019-09-11 14:32:47,456 - INFO - The logging module is working.
logging.warning(‘An error message is about to be logged.’)
2019-09-11 14:33:02,391 - WARNING - An error message is about to be logged.
logging.error(‘An error has occurred.’)
2019-09-11 14:33:14,413 - ERROR - An error has occurred.
logging.critical(‘The program is unable to recover!’)
2019-09-11 14:33:24,071 - CRITICAL - The program is unable to recover!

日志级别的好处在于,我们可以改变想看到的日志消息的优先级。比如说,向 basicConfig() 函数传入 logging.DEBUG 作为 level 关键字参数,这将显示所有级别为 DEBUG 的日志消息。当开发了更多的程序后,我们可能只对错误感兴趣,在这种情况下,可以将 basicConfig() 的 level 参数设置为 logging.ERROR,这将只显示 ERROR 和 CRITICAL 消息,跳过 DEBUG、INFO 和 WARNING 消息。

Python logging禁用日志

在调试完程序后,可能并不希望所有这些日志消息出现在屏幕上,这时就可以使用 logging.disable() 函数禁用这些日志消息,从而不必进入到程序中,手工删除所有的日志调用。

logging.disable() 函数的用法是,向其传入一个日志级别,它会禁止该级别以及更低级别的所有日志消息。因此,如果想要禁用所有日志,只要在程序中添加 logging.disable(logging.CRITICAL) 即可,例如:

import logging
logging.basicConfig(level=logging.INFO, format=’ %(asctime)s - %(levelname)s - %(message)s’)
logging.critical(‘Critical error! Critical error!’)
2019-09-11 14:42:14,833 - CRITICAL - Critical error! Critical error!
logging.disable(logging.CRITICAL)
logging.critical(‘Critical error! Critical error!’)
logging.error(‘Error! Error!’)

因为 logging.disable() 将禁用它之后的所有消息,所以可以将其添加到程序中更接近 import logging 的位置,这样更容易找到它,方便根据需要注释掉它,或取消注释,从而启用或禁用日志消息。

将日志消息输出到文件中

虽然日志消息很有用,但它们可能塞满屏幕,让你很难读到程序的输出。考虑到这种情况,可以将日志信息写入到文件,既能使屏幕保持干净,又能保存信息,一举两得。

将日志消息输出到文件中的实现方法很简单,只需要设置 logging.basicConfig() 函数中的 filename 关键字参数即可,例如:

import logging
logging.basicConfig(filename=‘demo.txt’, level=logging.DEBUG, format=’%(asctime)s - %(levelname)s - %(message)s’)

此程序中,将日志消息存储到了 demo.txt 文件中,该文件就位于运行的程序文件所在的目录。

Python assert调试程序

前面章节介绍了如何使用 IDLE 自身的调试工具调试程序,除此之外,Python 还提供了 assert 语句,也可以用来调试程序。

Python assert断言》一节中,已经对 assert 的基本用法做了简单介绍,assert 语句的完整语法格式为:

assert 条件表达式 [,描述信息]

assert 语句的作用是:当条件表达式的值为真时,该语句什么也不做,程序正常运行;反之,若条件表达式的值为假,则 assert 会抛出 AssertionError 异常。其中,[,描述信息] 作为可选参数,用于对条件表达式可能产生的异常进行描述。

例如:

s_age = input("请输入您的年龄:")
age = int(s_age)
assert 20 < age < 80 , "年龄不在 20-80 之间"
print("您输入的年龄在20和80之间")

程序运行结果为:

请输入您的年龄:10
Traceback (most recent call last):
File “C:\Users\mengma\Desktop\1.py”, line 3, in
assert 20 < age < 80 , “年龄不在 20-80 之间”
AssertionError: 年龄不在 20-80 之间

通过运行结果可以看出,当 assert 中条件表达式的值为假时,程序将抛出异常,并附带异常的描述性信息,与此同时,程序立即停止执行。

通常情况下,assert 可以和 try except 异常处理语句配合使用,以前面代码为例:

try:
    s_age = input("请输入您的年龄:")
    age = int(s_age)
    assert 20 < age < 80 , "年龄不在 20-80 之间"


**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/d164fe09ceb4d3919321092609212719.png)
![img](https://img-blog.csdnimg.cn/img_convert/bd4489690aa291b64a9447db3c4e542b.png)
![](https://img-blog.csdnimg.cn/img_convert/46506ae54be168b93cf63939786134ca.png)
![](https://img-blog.csdnimg.cn/img_convert/252731a671c1fb70aad5355a2c5eeff0.png)
![](https://img-blog.csdnimg.cn/img_convert/6c361282296f86381401c05e862fe4e9.png) 
![](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)**
![img](https://img-blog.csdnimg.cn/img_convert/3b87eec81ad745aca36ee054267cd710.png)

0490000)]
![](https://img-blog.csdnimg.cn/img_convert/46506ae54be168b93cf63939786134ca.png)
![](https://img-blog.csdnimg.cn/img_convert/252731a671c1fb70aad5355a2c5eeff0.png)
![](https://img-blog.csdnimg.cn/img_convert/6c361282296f86381401c05e862fe4e9.png) 
![](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)**
[外链图片转存中...(img-rioIR1bM-1712930490000)]

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值