Python学习(九)错误处理、调试、单元测试、doctest文档测试

在程序运行过程中,总会遇到各种各样的错误。

  • 有的错误是程序编写有问题造成的,比如本来应该输出整数结果输出了字符串,这种错误我们通常称之为 Bug,而 Bug 是必须修复的。

  • 有的错误是用户输入造成的,比如让用户输入 email 地址,结果得到一个空字符串,这种错误可以通过检查用户输入来做相应的处理。

  • 还有一类错误是完全无法在程序运行过程中预测的,比如写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络突然断掉了。这类错误也称为异常,在程序中通常是必须处理的,否则,程序会因为各种问题终止并推出。

Python 内置了一套 异常处理 机制,来帮助我们进行错误处理。

除了异常处理外,我们也需要跟踪程序的执行,查看变量的值是否正确,这个过程称为 调试。Python 的 pdb 可以让我们但不方式执行代码。

最后,编写 测试 也很重要。有了良好的测试,就可以在程序修改后反复运行,确保程序输出符合我们编写的测试。

一、错误处理

1)错误处理介绍

在程序运行的过程中,如果发生了错误,可以事先约定返回一个错误代码。这样,就可以知道是否有错,以及出错的原因。在操作系统提供的调用中,返回错误码非常常见。比如打开文件的函数 open(),成功时返回文件描述符(就是一个整数),出错时返回 -1

用错误码来表示是否出错十分不便,因为函数本身应该返回的正常结果和错误码混在一起,造成调用者必须用大量的代码来判断是否出错:

def foo():
    r = some_function()
    if r == (-1):
        return (-1)
    # do something
    return r

def bar():
    r = foo()
    if r == (-1):
        print('Error')
    else:
        pass

一旦出错,还要一级一级上报,直到某个函数可以处理该错误(比如,给用户输出一个错误信息)。

所以高级语言通常都内置了一套 try ... except ... finally ... 的错误处理机制,Python 也不例外。

什么是错误?

  • 错误 是指由于逻辑或算法错误等,导致一个程序已无法正常执行的 问题

什么是异常?

  • 异常 是程序出错时标识的一种 状态,当异常发生时,程序不会再向下执行,而从调用栈向上抛出,转去调用此函数的地方,待相应的错误处理后便恢复为正常状态。
2)错误处理语法

错误处理的关键字顺序如下:

try / except / else / finally 语句

错误处理的语法如下:

try:
  可能触发异常的语句
except 错误类型1 [as 变量1]
  异常处理语句1
except 错误类型2 [as 变量2]
  异常处理语句2
except (错误类型3, 错误类型4, …) [as 变量3]
  异常处理语句3

except:
  异常处理语句other
else:
  未发生异常语句
finally:
  最终语句

语法说明:

  • except子句:可以有一个或多个,但至少要有一个。
  • as子句 :是用于绑定错误对象的变量,可以省略。
  • else子句:最多只能有一个,也可以省略。
  • finally子句:最多只能有一个,也可以省略不写。

处理说明:

  1. except 子句用来捕获和处理当某种类型的错误发生时,处理异常。
  2. except 子句会根据错误的类型进行匹配,如匹配成功则调用异常处理语句进行处理,然后程序转为正常状态。
  3. 如果except子句没有匹配到任何类型的异常则转到except 子句。
  4. 如果没有任何except子句进行处理,则程序的异常状态会继续下去,并向上层传递。
  5. 如果没有异常,则执行else 子句中的语句。
  6. 最后执行finally子句中的语句。
3)异常类的继承层级

Python 中异常类的继承层级如下:

BaseException[所有内建异常类的基类]
+--SystemExit[sys.exit()函数引发的异常,异常不捕获处理,就直接交给python解释器,解释器退出]
+--KeyboardInterrupt[对应的捕获用户中断行为Ctrl+c]
+--GeneratorExit
+--Exception[是所有内建的,非系统退出的异常的基类,自定义异常应该继承自它]
    +--自定义异常[从Exception继承的类]
    +--RuntimeError
        +RecursionError
    +--MemoryError
    +--NameError[异常类(错误的类型)]
    +--StopIteration
    +--StopAsyncIteration
    +--ArithmeticError[所有算术计算引发的异常,其子类的除零异常等]
        +--FloatingPointError
        +--OverflowError
        +--ZeroDivisionError
    +--LookupError[使用映射的键或序列的索引无效时引发的异常的基类:IndexError,KeyError]
        +--IndexError
        +--KeyError
    +--SyntaxError[语法错误:Python将这种错误也归到异常类下面的Exception下的子类,但是这种错误是不可捕获的]
    +--OSError
        +--BlockingIOError
        +--ChildProcessError
        +--ConnectionError
            +--BrokenPipeError
            +--ConnectionAbortedError
            +--ConnectionRefusedError
            +--ConnectionResetError
        +--FileExistsError
        +--FileNotFoundError
        +--InterruptedError
        +--InterruptedError
        +--IsADirectoryError
        +--NotADirectoryError
        +--PermissionError
        +--PermissionError
        +--ProcessLookupError
        +--TimeoutError

二、调试

1)调试介绍

程序能一次写完并正常运行的概率很小,基本不超过 1%。总会有各种各样的 Bug 需要修改。有的 Bug 很简单,看看错误信息就知道,有的 Bug 则比较负责,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误的。因此,需要一整套调试程序的手段来修复 Bug。

第一种方法简单有效,就是用 print() 把可能有问题的变量打印出来看看,如下所示:

def foo(s):
    n = int(s)
    print('>>> n = %d' % n)
    return 10 / n

def main():
    foo('0')

main()

保存为 err.py 文件后执行,可以在输出中查看打印的变量值,如下所示:

C:\Users\lenovo>python err.py
>>> n = 0
Traceback (most recent call last):
  File "C:\Users\lenovo\err.py", line 9, in <module>
    main()
  File "C:\Users\lenovo\err.py", line 7, in main
    foo('0')
  File "C:\Users\lenovo\err.py", line 4, in foo
    return 10 / n
ZeroDivisionError: division by zero

print() 最大的坏处就是将来还得删掉它,想想程序里导出都是 print(),运行结果也会包含很多垃圾信息。所以,我们又有第二种方法。

2)assert 断言

只要是用 print() 来辅助查看的地方,都可以用 断言(assert) 来替代。如下所示:

def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n

def main():
    foo('0')

main()
  • assert 的意思是,表达式 n != 0 应该是 True,否则,根据程序运行的逻辑,后面的代码肯定会出错。

如果断言失败,assert 语句本身就会抛出 AssertionError。如下所示:

C:\Users\lenovo>python err.py
Traceback (most recent call last):
  File "C:\Users\lenovo\err.py", line 9, in <module>
    main()
  File "C:\Users\lenovo\err.py", line 7, in main
    foo('0')
  File "C:\Users\lenovo\err.py", line 3, in foo
    assert n != 0, 'n is zero!'
AssertionError: n is zero!

实际上,程序中如果到处充斥着 assert,和 print() 相比也好不到哪去。不过,启动 Python 解释器时可以用 -O (大写字母o)参数来关闭 assert。如下所示:

C:\Users\lenovo>python -O err.py
Traceback (most recent call last):
  File "C:\Users\lenovo\err.py", line 9, in <module>
    main()
  File "C:\Users\lenovo\err.py", line 7, in main
    foo('0')
  File "C:\Users\lenovo\err.py", line 4, in foo
    return 10 / n
ZeroDivisionError: division by zero
  • 可以看到,报错又从 AssertionError 变成 ZeroDivisionError 了。

关闭 assert 后,你可以把所有的 assert 语句当成 pass 来看。

3)logging 日志

print() 替换为 logging 是第3种方式,和 assert 相比,logging 不会抛出错误,而且可以输出到文件。如下所示:

import logging

s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)

执行结果如下:

C:\Users\lenovo>python err.py
Traceback (most recent call last):
  File "C:\Users\lenovo\err.py", line 6, in <module>
    print(10 / n)
ZeroDivisionError: division by zero
  • logging.info() 就可以输出一段文本。但是,运行后发现除了 ZeroDivisionError,并没有任何信息,怎么回事?

别急,在 import logging 之后添加一行配置再试试:

import logging
logging.basicConfig(level=logging.INFO)

看到输出了:

C:\Users\lenovo>python err.py
INFO:root:n = 0
Traceback (most recent call last):
  File "C:\Users\lenovo\e
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿放下技术的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值