这是机器未来的第22篇文章
原文首发地址:https://blog.csdn.net/RobotFutures/article/details/125454677
程序员经常自嘲,“面向BUG编程”,它不是玩笑,是真的!非常贴切!!!程序员基本上天天与BUG打交道,写BUG,改BUG, 写BUG,改BUG…无限循环。那么怎么驾驭BUG呢,今天来认识一下BUG!
我们根据程序编译时和运行时两种场景,将BUG区分为错误和异常。
错误又分为语法错误和逻辑错误。
# 语法错误
def add(a, b):
return a+b
File "C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_19160/942780934.py", line 2
def add(a, b)
^
SyntaxError: invalid syntax
如上提示语法错误,在def add(a, b)后面标识了个向上的三角符号,表示少了冒号。
x = 12
if x % 2 == 0:
print("x 是奇数")
else:
print("x 是偶数")
x 是奇数
这里都是展示的都是很简单的例子,一个真实的项目往往很复杂,业务逻辑复杂后,出现逻辑错误会相对难以定位问题点。但是足够细心也是可以快速定位问题的,将代码分块,给定预期输出,判断结果是否满足预期即可定位。项目开发完成后,往往会进行单元测试,覆盖所有可能的场景。
排除语法错误后,出现在执行时出现的问题,被称为异常。举个简单的例子:
a = 10
b = 0
a / b
print("程序结束")
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_19160/565678119.py in
1 a = 10
2 b = 0
----> 3 a / b
4 print("程序结束")
ZeroDivisionError: division by zero
当被除数为0时,程序抛出异常,并且停止运行,可以看到“程序结束”未打印输出,在处理之前就结束了。那么Python内部支持哪些异常呢?Python的异常是Exception类管理的,我们用Exception??看一下
Exception??
Init signature: Exception(self, /, *args, **kwargs)
Docstring: Common base class for all non-exit exceptions.
Type: type
Subclasses: TypeError, StopAsyncIteration, StopIteration, ImportError, OSError, EOFError, RuntimeError, NameError, AttributeError, SyntaxError, ...
可以看到它有很多子类:TypeError, StopAsyncIteration, StopIteration, ImportError, OSError, EOFError, RuntimeError, NameError, AttributeError, SyntaxError, …,每个子类代表一种错误或异常,我们刚才看的语法错误、除零错误都可以在它的子类中看到。
刚才我们提到,程序抛出异常时,程序会退出,在有一些场景,我希望即使数据异常,我仍然希望它继续运行,对于不符合要求的数据填充默认值进行处理,在爬虫数据抓取的过程中经常用到。
比如我想分析一下市场股票的走势,需要拉取很多股票的数据,但是因为各种原因,可能爬取的数据不全,有缺失的情况,加载到程序中处理,就会报数值错误、不能除零等等,对于这种情况我们会对数据进行预处理,不能满足输入条件的数据给予默认值,让他满足程序输入的要求,并保持程序继续运行,不直接退出。
这里要用到异常捕获语句:
try:
pass # 正常代码执行块
except ExceptionA:
pass # 异常类型A处理代码块
except ExceptionB:
pass # 异常类型B处理代码块
except:
pass # 捕获除了异常类型A和异常类型B之外所有异常代码块
else:
pass # 没有异常时代码块(可选)
finally:
pass # 不论是否有异常,都会执行的代码块
上面的异常捕获语句是最完整的结构,实际使用时按需选择,不一定需要全部支持,例如在程序设计时,对于外部输入一般要进行合法性校验,假如现在我们要输入年龄,该如何设计呢?
age = int(input('请输入一个你的年龄:'))
print(f"你的年龄为{age}")
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_19160/1653428413.py in
----> 1 age = int(input('请输入一个你的年龄:'))
2 print(f"你的年龄为{age}")
ValueError: invalid literal for int() with base 10: 'qw'
从上面的代码中可以看到,程序设计预期想输入的数据为整数类型,但是用户各种各样,它输入字母时就抛出异常了,那么怎么将程序设计的更加友好,更加人性化呢?
try:
age = int(input('请输入一个你的年龄:'))
print(f"你的年龄为{age}")
except:
print("您输入的数据类型不对,请输入整数!")
您输入的数据类型不对,请输入整数!
可以看到,程序不再生硬的抛出一堆代码异常给用户,而是友好的提示信息。下面演示程序中捕获多个异常:
try:
a = int(input("请输入除数a:"))
b = int(input("请输入被除数b:"))
c = a / b
except ValueError:
print("除数、被除数必须为整数!")
except ZeroDivisionError:
print("被除数不能为0!")
except Exception as e: # 未知异常的捕获
print(f"未识别的异常, 输入值为{a}, {b}, 异常信息:{e}")
else:
print(f"计算结果:{c}") # 未发生错误时输出计算结果
finally:
print("程序运行完毕")
计算结果:16.0
程序运行完毕
上面的例程捕获了多个异常ValueError和ZeroDivisionError,并且对于未知的异常也进行了管理,通过Exception捕获异常信息,并且同时记录了异常的输入信息,便于快速定位问题, 类似这样:
# 此输出在注释 ValueError和ZeroDivisionError 两个异常分支后输入32和0获得的。
未识别的异常, 输入值为32, 0, 异常信息:division by zero
程序运行完毕
上面的例程在没有异常发生时,通过else语句输出计算结果;
上面的例程不论是否发生异常,最终的finally语句都会被执行。
对于多个异常,也可以组合成一个处理逻辑,类似这样:
try:
a = int(input("请输入除数a:"))
b = int(input("请输入被除数b:"))
c = a / b
except (ValueError, ZeroDivisionError):
print("除数、被除数必须为整数!且被除数不能为0!")
except Exception as e: # 未知异常的捕获
print(f"未识别的异常, 输入值为{a}, {b}, 异常信息:{e}")
else:
print(f"计算结果:{c}") # 未发生错误时输出计算结果
finally:
print("程序运行完毕")
除数、被除数必须为整数!且被除数不能为0!
程序运行完毕
在捕获异常章节提到,在一些场景发生异常,在进行相关的处理后仍然可以继续运行,但有些场景,异常是致命的,它不能满足程序输入需求,需要直接退出程序,那么怎么处理呢?
这里用raise主动抛出异常,终止程序。
例如一个深度学习模型的标签数据的矩阵结构为(3, 1),但是输入形状为(3),程序将无法处理,应该立即抛出异常,并且告知原因以待修改。
import numpy as np
y = [1, 2, 3]
y_pred = [[1], [2], [3]]
y1 = np.array(y)
y2 = np.array(y_pred)
print(y1.shape, y2.shape)
if y1.shape[-1] == 1: # 检查最后一维是否为1
print("data shape is ok")
else:
raise ValueError(f"数据形状不一致,输入数据形状为{y1.shape}, 期望形状为{y2.shape}")
(3,) (3, 1)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_19160/4000627889.py in
12 print("data shape is ok")
13 else:
---> 14 raise ValueError(f"数据形状不一致,输入数据形状为{y1.shape}, 期望形状为{y2.shape}")
ValueError: 数据形状不一致,输入数据形状为(3,), 期望形状为(3, 1)
class MyError(Exception):
"""
自定义异常类
"""
def __init__(self, msg):
super(MyError, self).__init__
self.msg = msg
import numpy as np
y = [1, 2, 3]
y_pred = [[1], [2], [3]]
y1 = np.array(y)
y2 = np.array(y_pred)
print(y1.shape, y2.shape)
if y1.shape[-1] == 1: # 检查最后一维是否为1
print("data shape is ok")
else:
raise MyError(f"数据形状不一致,输入数据形状为{y1.shape}, 期望形状为{y2.shape}")
(3,) (3, 1)
---------------------------------------------------------------------------
MyError Traceback (most recent call last)
C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_19160/3613492880.py in
17 print("data shape is ok")
18 else:
---> 19 raise MyError(f"数据形状不一致,输入数据形状为{y1.shape}, 期望形状为{y2.shape}")
MyError: 数据形状不一致,输入数据形状为(3,), 期望形状为(3, 1)
捕获自定义异常类,如下的代码可以看到,自定义异常类是可以被捕获到的,而且增加捕获程序后,输出信息更加友好。
class MyError(Exception):
"""
自定义异常类
"""
def __init__(self, msg):
super(MyError, self).__init__
self.msg = msg
import numpy as np
y = [1, 2, 3]
y_pred = [[1], [2], [3]]
y1 = np.array(y)
y2 = np.array(y_pred)
print(y1.shape, y2.shape)
try:
if y1.shape[-1] == 1: # 检查最后一维是否为1
print("data shape is ok")
else:
raise MyError(f"数据形状不一致,输入数据形状为{y1.shape}, 期望形状为{y2.shape}")
except Exception as e:
print(e)
(3,) (3, 1)
数据形状不一致,输入数据形状为(3,), 期望形状为(3, 1)
以上就是Python异常处理的基础知识了。后面列一些常见的异常目录。
BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
IOError 输入/输出操作失败
OSError 操作系统错误
WindowsError 系统调用失败
ImportError 导入模块/对象失败
LookupError 无效数据查询的基类
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
UnboundLocalError 访问未初始化的本地变量
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
SyntaxError Python 语法错误
IndentationError 缩进错误
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
TypeError 对类型无效的操作
ValueError 传入无效的参数
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 警告的基类
DeprecationWarning 关于被弃用的特征的警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告
《Python零基础快速入门系列》快速导航: