Python用异常对象(exception object)表示异常情况,遇到错误后,会引发异常。如果异常对象并未被处理或捕捉,程序就会用所谓的回溯(Traceback,一种错误信息)终止执行
可以简单的理解异常处理机制,就是在程序运行出现错误时,让 Python 解释器执行事先准备好的除错程序,进而尝试恢复程序的执行。
借助异常处理机制,甚至在程序崩溃前也可以做一些必要的工作,例如将内存中的数据写入文件、关闭打开的文件、释放分配的内存等。
Python 异常处理机制会涉及 try、except、else、finally 这 4 个关键字,同时还提供了可主动使程序引发异常的 raise 语句,
"""
捕获异常组合
try:
可能发生错误的代码
except:
如果出现异常执行的代码
如果出现的异常捕获到了, 那么不会影响后续的代码执行
"""
f = open('你好1.txt', mode='r', encoding='utf-8') # r模式 是不会自动创建不存在的文件
print(f) # 报错 FileNotFoundError: [Errno 2] No such file or directory: '你好1.txt'
try:
f = open('你好1.txt', mode='r', encoding='utf-8')
except:
f = open('你好1.txt', mode='w', encoding='utf-8')
# 先赋值声明后使用
print(name)
"""
NameError: name 'name' is not defined
名称错误:name 没有被定义
"""
# 被除数是不能为0的
print(10 / 0)
"""
ZeroDivisionError: division by zero
除零错误:任何数值被零除都会导致ZeroDivisionError错误
"""
# 索引错误
a = [1, 3, 4, 5]
print(a[20])
"""
IndexError: list index out of range
索引错误:使用一个超出范围的值索引时引发
"""
dic1 = {
'age': 18,
'name': '泽言'
}
print(dic1['kk'])
"""
KeyError: 'kk'
键错误:该字典没有这个键引发的报错
"""
# 捕获错误
try:
print(10 / 0)
print(name)
except NameError: # 指定的捕获异常
print('有错误')
# 捕获多个异常
try:
print(10 / 0)
print(name)
except (NameError, ZeroDivisionError): # 指定的捕获异常
print('有错误')
# 捕获所有异常
try:
print(10 / 0)
print(name)
except Exception: # 捕捉所有异常(你打印的错误要是我当前Exception里面收录好了) 记录了 99%
print('有错误')
# 显示打印异常的信息
try:
print(10 / 0)
print(name) # 如果存在多个异常,他只会打印第一个异常信息,但是能捕捉所有的异常
except Exception as e: # 只是一个变量,名字随便取
print(e)
在原本的 try except 结构的基础上,Python 异常处理机制还提供了一个 else 块,也就是原有 try except 语句的基础上再添加一个 else 块,即try except else结构。
使用 else 包裹的代码,只有当 try 块没有捕获到任何异常时,才会得到执行;反之,如果 try 块捕获到异常,即便调用对应的 except 处理完异常,else 块中的代码也不会得到执行。
# else 当 try 代码块下面的代码没有异常的时候才会被执行
try:
print(111)
except Exception as r:
print(r)
else:
print('我是else,当try代码块下面的代码没有异常的时候我会被执行')
''' 示例 '''
try:
result = 20 / int(input('请输入除数:'))
print(result)
except ValueError:
print('必须输入整数')
except ArithmeticError:
print('算术错误,除数不能为 0')
else:
print('没有出现异常')
print("继续执行")
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 块是否发生异常,该块中的代码都会被执行。
# finally 不管 try 代码块下的代码有没有异常,都会被执行
try:
print(name)
except Exception as r:
print(r)
else:
print('我是else,当try代码块下面的代码没有异常的时候我会被执行')
finally:
print('我是finally, 不管try代码块下面的代码有没有异常, 我都会被执行')
是否可以在程序的指定位置手动抛出一个异常?
答案是肯定的,Python 允许我们在程序中手动设置异常,使用 raise 语句即可。
你可能会疑惑,从来都是想方设法地让程序正常运行,怎么还要手动设置异常呢?
首先要分清楚程序发生异常和程序执行错误,它们完全是两码事:程序由于错误导致的运行异常,是需要程序员想办法解决的;但还有一些异常,是程序正常运行的结果,比如用 raise 手动引发的异常。
当程序出现错误,python会自动引发异常,也可以通过raise显示地引发异常。一旦执行了raise语句,raise后面的语句将不能执行。
raise [exceptionName [(reason)]]
用 [] 括起来的为可选参数,其作用是指定抛出的异常名称,以及异常信息的相关描述。如果可选参数全部省略,则 raise 会把当前错误原样抛出;如果仅省略 (reason),则在抛出异常时,将不附带任何的异常描述信息。
''' raise的三种情况 '''
# 1. 单独一个 raise
# 单独的raise会重新触发前一个异常,如果之前没有触发异常,触发RuntimeError
# 第一种情况:上下文中捕获的异常
def test(num):
try:
100/num
except Exception as res:
print("捕获异常完成")
raise
test(0)
# 第二种情况:默认引发RuntimeError 异常:
def test(num):
if num == 100:
raise
print(num)
test(100)
# 2. raise 异常名称
def Test1(num):
try:
100/num
except Exception as res:
print("捕获异常完成")
print(res)
raise ZeroDivisionError
print("----在raise之后,不会执行-----")
else:
print("没有异常")
Test1(0)
# 其中 异常名称可以为自定义异常名称
class CustomException(Exception):
def __init__(self,ErrorInfo):
self.ErrorInfo = ErrorInfo
def __str__(self):
return self.ErrorInfo
def Test1(num):
try:
raise CustomException('这是一个自定义的异常')
except CustomException as res:
print(res)
Test1(0)
# 3.raise 异常名称(异常描述信息)
def Test1(num):
try:
100/num
except Exception as res:
print("捕获异常完成")
print(res)
raise ZeroDivisionError("分母不能为0")
print("----在raise之后,不会执行-----")
else:
print("没有异常")
Test1(0)
''' 示例 '''
def div(a, c):
try:
if c == 0:
raise Exception('c不能等于0') # 主动抛出的一个异常,不是系统报错
return a / c
except Exception as e:
print(e) # 打印报错信息
# print(div(5,0))
print(div(7, 6))
''' 示例 '''
try:
a = input("输入一个数:")
#判断用户输入的是否为数字
if(not a.isdigit()):
raise ValueError("输入的%s不是数字,要求必须输入数字"%(a))
except ValueError as e:
print("引发异常:", repr(e))
print("输错了,异常发生,本程序也没有崩溃,正常运行到结束!!!")
在实际调试程序的过程中,有时只获得异常的类型是远远不够的,还需要借助更详细的异常信息才能解决问题。
捕获异常时,有 2 种方式可获得更多的异常信息,分别是:
使用 sys 模块中的 exc_info() 方法获得更多的异常信息。
后续我们会接受模块的详细信息,先简单了解一下。
模块 sys 中,有两个方法可以返回异常的全部信息,分别是 exc_info() 和 last_traceback(),这两个函数有相同的功能和用法,以 exc_info() 方法为例。
exc_info() 方法会将当前的异常信息以元组的形式返回,该元组中包含 3 个元素,分别为 type、value 和 traceback,它们的含义分别是:
# 使用 sys 模块之前,需使用 import 引入
import sys
try:
x = int(input("请输入一个被除数:"))
print("30除以", x, "等于", 30 / x)
except:
print(sys.exc_info())
print("其他异常...")
# 当输入 0 时,程序运行结果为:
# 请输入一个被除数:0
# (<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero',), <traceback object at 0x000001FCF638DD48>)
# 其他异常...
'''
输出结果中,第 2 行是抛出异常的全部信息,这是一个元组,有 3 个元素,第一个元素是一个 ZeroDivisionError 类;第 2 个元素是异常类型 ZeroDivisionError 类的一个实例;第 3 个元素为一个 traceback 对象。其中,通过前 2 个元素可以看出抛出的异常类型以及描述信息,对于第 3 个元素,是一个 traceback 对象,无法直接看出有关异常的信息,还需要对其做进一步处理。
要查看 traceback 对象包含的内容,需要先引进 traceback 模块,然后调用 traceback 模块中的 print_tb 方法,并将 sys.exc_info() 输出的 traceback 对象作为参数参入。例如:
'''
# 使用 sys 模块之前,需使用 import 引入
import sys
# 引入traceback模块
import traceback
try:
x = int(input("请输入一个被除数:"))
print("30除以", x, "等于", 30 / x)
except:
# print(sys.exc_info())
traceback.print_tb(sys.exc_info()[2])
print("其他异常...")
# 输入 0,程序运行结果为:
# 请输入一个被除数:0
# File "C:\Users\mengma\Desktop\demo.py", line 7, in <module>
# print("30除以",x,"等于",30/x)
# 其他异常...
# 可以看到,输出信息中包含了更多的异常信息,包括文件名、抛出异常的代码所在的行数、抛出异常的具体代码。
除了使用 sys.exc_info() 方法获取更多的异常信息之外,还可以使用 traceback 模块,该模块可以用来查看异常的传播轨迹,追踪异常触发的源头
class SelfException(Exception):
pass
def main():
firstMethod()
def firstMethod():
secondMethod()
def secondMethod():
thirdMethod()
def thirdMethod():
raise SelfException("自定义异常信息")
main()
'''
上面程序中 main() 函数调用 firstMethod(),firstMethod() 调用 secondMethod(),secondMethod() 调用 thirdMethod(),thirdMethod() 直接引发一个 SelfException 异常。
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\1.py", line 11, in <module>
main()
File "C:\Users\mengma\Desktop\1.py", line 4, in main <--mian函数
firstMethod()
File "C:\Users\mengma\Desktop\1.py", line 6, in firstMethod <--第三个
secondMethod()
File "C:\Users\mengma\Desktop\1.py", line 8, in secondMethod <--第二个
thirdMethod()
File "C:\Users\mengma\Desktop\1.py", line 10, in thirdMethod <--异常源头
raise SelfException("自定义异常信息")
SelfException: 自定义异常信息
'''
从输出结果可以看出,异常从 thirdMethod() 函数开始触发,传到 secondMethod() 函数,再传到 firstMethod() 函数,最后传到 main() 函数,在 main() 函数止,这个过程就是整个异常的传播轨迹。
在实际应用程序的开发中,大多数复杂操作都会被分解成一系列函数或方法调用。这是因为,为了具有更好的可重用性,会将每个可重用的代码单元定义成函数或方法,将复杂任务逐渐分解为更易管理的小型子任务。由于一个大的业务功能需要由多个函数或方法来共同实现,在最终编程模型中,很多对象将通过一系列函数或方法调用来实现通信,执行任务。
所以,当应用程序运行时,经常会发生一系列函数或方法调用,从而形成“函数调用战”。异常的传播则相反,只要异常没有被完全捕获(包括异常没有被捕获,或者异常被处理后重新引发了新异常),异常就从发生异常的函数或方法逐渐向外传播,首先传给该函数或方法的调用者,该函数或方法的调用者再传给其调用者,直至最后传到 Python 解释器,此时 Python 解释器会中止该程序,并打印异常的传播轨迹信息。
很多初学者一看到输出结果所示的异常提示信息,就会惊慌失措,他们以为程序出现了很多严重的错误,其实只有一个错误,系统提示那么多行信息,只不过是显示异常依次触发的轨迹。
其实,上面程序的运算结果显示的异常传播轨迹信息非常清晰,它记录了应用程序中执行停止的各个点。最后一行信息详细显示了异常的类型和异常的详细消息。从这一行向上,逐个记录了异常发生源头、异常依次传播所经过的轨迹,并标明异常发生在哪个文件、哪一行、哪个函数处。
使用 traceback 模块查看异常传播轨迹,首先需要将 traceback 模块引入,该模块提供了如下两个常用方法:
可能有读者好奇,从上面方法看不出它们到底处理哪个异常的传播轨迹信息。实际上我们常用的 print_exc() 是 print_exc([limit[, file]]) 省略了 limit、file 两个参数的形式。而 print_exc([limit[, file]]) 的完整形式是 print_exception(etype, value, tb[,limit[, file]]),在完整形式中,前面三个参数用于分别指定异常的如下信息:
当程序处于 except 块中时,该 except 块所捕获的异常信息可通过 sys 对象来获取,其中 sys.exc_type、sys.exc_value、sys.exc_traceback 就代表当前 except 块内的异常类型、异常值和异常传播轨迹。
简单来说, print_exc([limit[, file]]) 相当于如下形式:
print_exception(sys.exc_etype, sys.exc_value, sys.exc_tb[, limit[, file]])
也就是说,使用 print_exc([limit[, file]]) 会自动处理当前 except 块所捕获的异常。该方法还涉及两个参数:
limit:用于限制显示异常传播的层数,比如函数 A 调用函数 B,函数 B 发生了异常,如果指定 limit=1,则只显示函数 A 里面发生的异常。如果不设置 limit 参数,则默认全部显示。
file:指定将异常传播轨迹信息输出到指定文件中。如果不指定该参数,则默认输出到控制台。
借助于 traceback 模块的帮助,我们可以使用 except 块捕获异常,并在其中打印异常传播信息,包括把它输出到文件中。例如如下程序:
# 导入trackback模块
import traceback
class SelfException(Exception): pass
def main():
firstMethod()
def firstMethod():
secondMethod()
def secondMethod():
thirdMethod()
def thirdMethod():
raise SelfException("自定义异常信息")
try:
main()
except:
# 捕捉异常,并将异常传播信息输出控制台
traceback.print_exc()
# 捕捉异常,并将异常传播信息输出指定文件中
traceback.print_exc(file=open('log.txt', 'a'))
上面程序第一行先导入了 traceback 模块,接下来程序使用 except 捕获程序的异常,并使用 traceback 的 print_exc() 方法输出异常传播信息,分别将它输出到控制台和指定文件中。
运行上面程序,同样可以看到在控制台输出异常传播信息,而且在程序目录下生成了一个 log.txt 文件,该文件中同样记录了异常传播信息。
使用异常处理的优势、便捷之处,本节将进一步从程序性能优化、结构优化的角度给出异常处理的一般规则。成功的异常处理应该实现如下4个目标。
为了实现以上目标,我们应做到:
例如:
使用 assert 断言语句是一个非常好的习惯,python assert 断言句语格式及用法很简单。在没完善一个程序之前,我们不知道程序在哪里会出错,与其让它在运行到最后崩溃,不如在出现错误条件时就崩溃,这样在跑大规模程序的时候不会过多的浪费时间和计算资源,这时候就需要assert断言的帮助。
def div(a, c):
# assert 后面的布尔结果为False就会触发断言 后面的布尔结果为True就不会触发断言
print(c != 0) # True
assert c != 0, 'c不能为0,kkk+3344' # 这个是我们自己定义报错,不是系统自己的报错
return a / c
# print(div(8,0))
print(div(5, 7)) # 0.7142857142857143
''' 示例 '''
def zero(s):
a = int(s)
assert a > 0,"a超出范围" #这句的意思:如果a确实大于0,程序正常往下运行
return a
zero("-2") #但是如果a是小于0的,程序会抛出AssertionError错误,报错为参数内容“a超出范围”
Pycharm的用户,通常都喜欢Pycharm强大的debug配置。但pycharm虽好,但需要图形化的UI界面,事实上在很多项目开发上有UI界面是很奢侈的事情。所以在无界面的vim下debug,难道只能用print了?那效率就太低下了。
事实上,python有内置的断点工具,直接在代码中插入即可完成断点调试。
arr = [1, 2, 3, 4, 5]
index = 0
def print_item(item):
print(item)
while index < len(arr):
print_item(arr[index])
index += 1
# 步过 会一步一步单步的调试代码 但是遇到函数是不会进入的
# 步入 可以进入到我们的内函数内部去执行操作
# 单步执行我的代码 他会一步一步的执行,遇到函数是会自动进入的
# 步出 是可以跳出函数内部的执行
# 运行到我的光标出 你的鼠标光标在哪里name他就会运行到哪里
for i in range(10): # 起始值值不写默认从0开始 ,左闭右开的结果
print(i)
1、在代码区域,鼠标右键,选中Debug开始调试(推荐)
2、点击代码区域右上角的虫子图标
注意选择正确的文件,在虫子图标的左侧,如选错了,就是给其他文件Debug了
开始进入Debug调试模式后,程序会运行到第一个断点,可以在代码后面或者Debugger工具面板查看变量值
调试过程中,如果想逐行执行代码并查看每一行的执行结果,但不希望进入函数调用的内部代码,而是直接执行完整个函数并跳过它 ,就可以使用它,在不存在子函数的情况下是和Step Into效果一样的(简而言之,越过子函数,但子函数会执行)
举个例子,有以下Python代码,设置断点, 点击 Debugger 窗口的 “Step Over” 按钮(或按下 F8 键) ,可以看到没有进入multiply(a,y)函数,直接计算出product_result的值了。
单步执行,遇到子函数就进入并且继续单步执行(简而言之,进入子函数)。
举个例子,有以下Python代码,设置断点, 点击 Debugger 窗口的 “Step Into” 按钮(或按下 F7 键) ,可以看到进入multiply(a,y)函数了。
强制进入,在调试的时候能进入任何方法, 它允许你在遇到多个可以进入的函数调用时,自行选择要进入的函数,而不是按照默认的逐层进入顺序。
举个例子, 有以下Python代码,假设你在调试模式下启动了这段代码,并在 sum_result = add(num1, num2) 这一行设置了断点。当代码停在这个断点处时,你可以点击 Debugger 窗口的 “Step Into” 按钮(或按下 F7 键)来进入 add() 函数的内部。
现在,如果你想进入 divide(product_result, num2) 这个函数调用的内部,你可以在 Debugger 窗口中右键点击 divide 这一行,然后选择 “Force Step Into”。这将会强制 Debugger 跳过 multiply() 函数内部的逐行执行,直接进入 divide() 函数内部。
当单步执行到子函数内时,用Step Out就可以执行完子函数余下部分,并返回到上一层函数。 “Step Over” 是逐行执行并忽略函数内部的操作,而 “Step Out” 是从当前函数内部退出并继续执行到调用点的操作。
举个例子,有以下Python代码,在sum_result = add(num1, num2)处打断点,代码执行进入add函数后,点击Step Out,直接跳出add函数,但是还是会计算出sum_result的值。
一直执行,到光标处停止,用在循环内部时,点击一次就执行一个循环。它是一个非常实用的功能,可以帮助你快速运行代码片段,跳过不必要的中间步骤,以及在你只关心特定部分的情况下更高效地进行调试。
举个例子,有以下Python代码,假设你在调试模式下启动了这段代码,并在 num1 = 5 这一行设置了断点。现在,如果你想要跳过 add(num1, num2) 函数调用的逐步执行,直接执行到 print(“Sum:”, sum_result) 这一行,你可以在该行上右键点击,然后选择 “Run to Cursor”。
这将会让程序在当前位置直接运行,直到光标所在的位置。在这个例子中,它将跳过 add() 函数的内部执行,直接执行到 print(“Sum:”, sum_result) 这一行,然后停止。
计算表达式。 这个功能对于查看变量、计算表达式、验证假设以及调试代码时的实时观察非常有帮助,可以动态地在断点处计算和查看不同表达式的值,更好地理解代码在不同步骤的状态,以及发现潜在的问题。
举个例子,有以下Python代码,假设你在调试模式下启动了这段代码,并在 sum_result = add(num1, num2) 这一行设置了断点。当代码停在这个断点处时,你可以使用 “Evaluate Expression” 功能来计算和查看一些表达式的值。
例如,你可以在调试器的窗口中找到 “Evaluate Expression” 输入框,然后输入表达式 num1 + num2 并点击 “Evaluate” 按钮。这将会计算 num1 和 num2 的和,然后显示结果。
允许你从一个断点处恢复程序的执行,跳过你不想逐步检查的部分,以便你能够更快地观察代码的整体行为。
比如,你在第20行和26行有两个断点,当前运行至第20行,按F9,则运行到下一个断点(即第26行),再按F9,则运行完整个流程,因为后面已经没有断点了。
一次性临时禁用所有断点,而不必逐个去禁用每个断点 ,在不删除断点的情况下,暂时停止断点的触发,以便在某些特定情况下进行调试或测试时非常有用。
选择这个后,所有断点变为灰色,断点失效,按F9则可以直接运行完程序。再次点击,断点变为红色,有效。