• 【Python零基础入门笔记 | 12】程序员为什么自嘲面向Bug编程?


    这是机器未来的第22篇文章

    原文首发地址:https://blog.csdn.net/RobotFutures/article/details/125454677

    1. 概述

    程序员经常自嘲,“面向BUG编程”,它不是玩笑,是真的!非常贴切!!!程序员基本上天天与BUG打交道,写BUG,改BUG, 写BUG,改BUG…无限循环。那么怎么驾驭BUG呢,今天来认识一下BUG!

    2. 错误与异常

    我们根据程序编译时和运行时两种场景,将BUG区分为错误和异常。

    2.1 错误

    错误又分为语法错误和逻辑错误。

    • 语法错误是编译时,编译器直接报错的类型,这种类型是比较简单的,编译器会直接报错,直接解决就行,例如
    # 语法错误
    def add(a, b):
        return a+b
    
    • 1
    • 2
    • 3
      File "C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_19160/942780934.py", line 2
        def add(a, b)
                     ^
    SyntaxError: invalid syntax
    
    • 1
    • 2
    • 3
    • 4

    如上提示语法错误,在def add(a, b)后面标识了个向上的三角符号,表示少了冒号。

    • 逻辑错误
      逻辑错误编译器不会报错,程序可以正常运行,但是拿不到预期的结果。例如计算奇偶数,本来设计的是整除2为偶数,其余为奇数,但是判断条件设置错误,导致不能获得正确的值。
    x = 12
    if x % 2 == 0:
        print("x 是奇数")
    else:
        print("x 是偶数")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    x 是奇数
    
    • 1

    这里都是展示的都是很简单的例子,一个真实的项目往往很复杂,业务逻辑复杂后,出现逻辑错误会相对难以定位问题点。但是足够细心也是可以快速定位问题的,将代码分块,给定预期输出,判断结果是否满足预期即可定位。项目开发完成后,往往会进行单元测试,覆盖所有可能的场景。

    2.2 异常

    排除语法错误后,出现在执行时出现的问题,被称为异常。举个简单的例子:

    a = 10
    b = 0
    a / b
    print("程序结束")
    
    • 1
    • 2
    • 3
    • 4
    ---------------------------------------------------------------------------
    
    ZeroDivisionError                         Traceback (most recent call last)
    
    C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_19160/565678119.py in <module>
          1 a = 10
          2 b = 0
    ----> 3 a / b
          4 print("程序结束")
    
    
    ZeroDivisionError: division by zero
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当被除数为0时,程序抛出异常,并且停止运行,可以看到“程序结束”未打印输出,在处理之前就结束了。那么Python内部支持哪些异常呢?Python的异常是Exception类管理的,我们用Exception??看一下

    Exception??
    
    • 1
    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, ...
    
    • 1
    • 2
    • 3
    • 4

    可以看到它有很多子类:TypeError, StopAsyncIteration, StopIteration, ImportError, OSError, EOFError, RuntimeError, NameError, AttributeError, SyntaxError, …,每个子类代表一种错误或异常,我们刚才看的语法错误、除零错误都可以在它的子类中看到。

    3. 异常处理

    刚才我们提到,程序抛出异常时,程序会退出,在有一些场景,我希望即使数据异常,我仍然希望它继续运行,对于不符合要求的数据填充默认值进行处理,在爬虫数据抓取的过程中经常用到。

    比如我想分析一下市场股票的走势,需要拉取很多股票的数据,但是因为各种原因,可能爬取的数据不全,有缺失的情况,加载到程序中处理,就会报数值错误、不能除零等等,对于这种情况我们会对数据进行预处理,不能满足输入条件的数据给予默认值,让他满足程序输入的要求,并保持程序继续运行,不直接退出。

    这里要用到异常捕获语句:

    try:            
        pass            # 正常代码执行块
    except ExceptionA:
        pass            # 异常类型A处理代码块
    except ExceptionB:
        pass            # 异常类型B处理代码块
    except:
        pass            # 捕获除了异常类型A和异常类型B之外所有异常代码块
    else:
        pass            # 没有异常时代码块(可选)
    finally:
        pass            # 不论是否有异常,都会执行的代码块
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面的异常捕获语句是最完整的结构,实际使用时按需选择,不一定需要全部支持,例如在程序设计时,对于外部输入一般要进行合法性校验,假如现在我们要输入年龄,该如何设计呢?

    age = int(input('请输入一个你的年龄:'))
    print(f"你的年龄为{age}")
    
    • 1
    • 2
    ---------------------------------------------------------------------------
    
    ValueError                                Traceback (most recent call last)
    
    C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_19160/1653428413.py in <module>
    ----> 1 age = int(input('请输入一个你的年龄:'))
          2 print(f"你的年龄为{age}")
    
    
    ValueError: invalid literal for int() with base 10: 'qw'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    从上面的代码中可以看到,程序设计预期想输入的数据为整数类型,但是用户各种各样,它输入字母时就抛出异常了,那么怎么将程序设计的更加友好,更加人性化呢?

    try:
        age = int(input('请输入一个你的年龄:'))
        print(f"你的年龄为{age}")
    except:
        print("您输入的数据类型不对,请输入整数!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    您输入的数据类型不对,请输入整数!
    
    • 1

    可以看到,程序不再生硬的抛出一堆代码异常给用户,而是友好的提示信息。下面演示程序中捕获多个异常:

    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("程序运行完毕")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    计算结果:16.0
    程序运行完毕
    
    • 1
    • 2

    上面的例程捕获了多个异常ValueError和ZeroDivisionError,并且对于未知的异常也进行了管理,通过Exception捕获异常信息,并且同时记录了异常的输入信息,便于快速定位问题, 类似这样:

    # 此输出在注释 ValueError和ZeroDivisionError 两个异常分支后输入32和0获得的。
    未识别的异常, 输入值为32, 0, 异常信息:division by zero
    程序运行完毕
    
    • 1
    • 2
    • 3

    上面的例程在没有异常发生时,通过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("程序运行完毕")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    除数、被除数必须为整数!且被除数不能为0!
    程序运行完毕
    
    • 1
    • 2

    4. 抛出异常

    在捕获异常章节提到,在一些场景发生异常,在进行相关的处理后仍然可以继续运行,但有些场景,异常是致命的,它不能满足程序输入需求,需要直接退出程序,那么怎么处理呢?

    这里用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}")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    (3,) (3, 1)
    
    
    
    ---------------------------------------------------------------------------
    
    ValueError                                Traceback (most recent call last)
    
    C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_19160/4000627889.py in <module>
         12     print("data shape is ok")
         13 else:
    ---> 14     raise ValueError(f"数据形状不一致,输入数据形状为{y1.shape}, 期望形状为{y2.shape}")
    
    
    ValueError: 数据形状不一致,输入数据形状为(3,), 期望形状为(3, 1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5. 自定义异常

    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}")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    (3,) (3, 1)
    
    
    
    ---------------------------------------------------------------------------
    
    MyError                                   Traceback (most recent call last)
    
    C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_19160/3613492880.py in <module>
         17     print("data shape is ok")
         18 else:
    ---> 19     raise MyError(f"数据形状不一致,输入数据形状为{y1.shape}, 期望形状为{y2.shape}")
    
    
    MyError: 数据形状不一致,输入数据形状为(3,), 期望形状为(3, 1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    捕获自定义异常类,如下的代码可以看到,自定义异常类是可以被捕获到的,而且增加捕获程序后,输出信息更加友好。

    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)            
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    (3,) (3, 1)
    数据形状不一致,输入数据形状为(3,), 期望形状为(3, 1)
    
    • 1
    • 2

    6.异常处理注意事项与建议

    6.1 注意事项

    • 只执行最先匹配的一个except
    • 如果父类异常在最前面,会吞噬所有子类异常
    • 多except注意:
      • 只会匹配一个except
      • 要先写子类异常再写父类异常
      • 如果except捕获的错误与触发的错误不一致,程序会捕获不到

    6.2 使用建议

    • 不建议使用异常来代替常规的检查,如if…else判断
    • 避免过多依赖于异常处理机制
    • 在必要的时候,可以手动引发异常(raise)=> 函数或方法
    • 在函数中,需要注意在try/except/finally使用return
      • 在finally中使用return,异常无法回溯
      • 在函数中的try/except语句使用return后,仍然会执行finally中的内容

    以上就是Python异常处理的基础知识了。后面列一些常见的异常目录。

    7. 附录 常见异常

    • 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零基础快速入门系列》快速导航:

  • 相关阅读:
    学习c++的第十四天
    数商云采购协同系统邀请招标实施步骤 | 助力建筑工程企业采购工作降本增效
    【LeetCode】经典的环形链表
    go实战(1)-hello,world与包基础(1)-模块基础
    useragent在线查找
    python计算离散积分
    看Spring源码不得不会的@Enable模块驱动实现原理讲解
    python打包exe
    【设计模式】单例模式
    unity基础学习二十,c#算法常用函数
  • 原文地址:https://blog.csdn.net/RobotFutures/article/details/125454677