python笔记-错误和测试

错误处理

try

1
2
3
4
5
6
7
8
9
10
11
12
13
try:
print('try...')
r = 10 / int('2')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('no error!')
finally:
print('finally...')
print('END')

假如我们认为某些代码可能回出错,就可以用try来运行这段代码

如果执行出错,则后续代码不再执行,而是直接跳转至错误捕获代码,即except语句块,

else会在try执行完毕且没有抛出异常时执行

不论有无异常抛出,finally都会执行,它会在try…except…else…都执行完后再执行

错误

python的错误也是类,所有错误类型都继承自BaseException,所以在使用except时,它会把定义的错误类型以及其所有子类一并捕获

1
2
3
4
5
6
try:
foo()
except ValueError as e:
print('ValueError')
except UnicodeError as e:
print('UnicodeError')

这里UnicodeErrorValueError的子类,所以第二个except是永远都捕获不到的,因为第一个except已经把父类给捕获了.

点击查看常见错误类型及继承关系

跨层调用

1
2
3
4
5
6
7
8
9
10
11
12
13
def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
try:
bar('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')

比如上面bar()调用foo(),main()调用bar().假如foo()出错,在main()中可以直接捕获到.

异常栈

1
2
3
4
5
6
7
8
9
10
11
# err.py:
def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
bar('0')

main()

假如错误没有被捕获,它会一直往上抛,知道被python解释器捕获,然后打印错误信息并退出程序

1
2
3
4
5
6
7
8
9
10
Traceback (most recent call last): # 告诉我们这是错误的跟踪信息
File "err.py", line 11, in <module> # 调用main()出错,在代码文件err.py的第11行,但原因在第9行
main()
File "err.py", line 9, in main # 调用bar('0')出错,在代码文件err.py的第9行,但原因在第6行
bar('0')
File "err.py", line 6, in bar # 原因是return foo(s) * 2这个语句出错了但还不是最终原因
return foo(s) * 2
File "err.py", line 3, in foo # return 10 / int(s)这个语句出错了,这是错误产生的源头,因为下面打印了具体错误
return 10 / int(s)
ZeroDivisionError: division by zero # 根据错误类型ZeroDivisionError,我们判断,int(s)本身并没有出错,但是int(s)返回0,在计算10 / 0时出错,至此,找到错误源头。

这就是异常栈,出错了向大佬提问时记得把完整异常栈附上.

记录错误

python内置的logging模块可以轻松记录错误信息,而且程序出错后会继续执行,正常退出.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import logging
def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
try:
bar('0')
except Exception as e:
logging.exception(e) # 捕获错误栈

main()
print('END')
1
2
3
4
5
6
7
8
9
10
11
# 执行后输出如下
ERROR:root:division by zero
Traceback (most recent call last):
File "err_logging.py", line 13, in main
bar('0')
File "err_logging.py", line 9, in bar
return foo(s) * 2
File "err_logging.py", line 6, in foo
return 10 / int(s)
ZeroDivisionError: division by zero
END

具体例子

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
# enconding=utf-8
import logging
import sys

logging.basicConfig(filename='app.log', level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s', encoding='utf-8')

def divide(x, y):
try:
result = x / y
return result
except Exception as e:
print(f"发生异常: {e}", file=sys.stderr) # 获取错误并输出到console的标准错误
logging.error(f"发生异常: {e}", exc_info=True) # 记录到日志文件
return None

# 示例用法
num1 = 10
num2 = 0

result = divide(num1, num2)

if result is not None:
print(f"结果: {result}")

print('Done')
1
2
Done
发生异常: division by zero

raise

抛出自己定义的错误

错误也是class,捕获一个错误本质上就是捕获该class的一个实例,因此错误并不是凭空产生的,而是有意创建并抛出的.python的内置函数会抛出很多类型的错误,我们也可以自己抛出错误.(但一般来说,尽量使用python内置的错误类型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FooError(ValueError):
pass

def foo(s):
n = int(s)
if n==0:
raise FooError(f'invalid value: {s}')
raise 10 / n

foo('0')

>>>
Traceback (most recent call last):
File "err_throw.py", line 11, in <module>
foo('0')
File "err_throw.py", line 8, in foo
raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0

向上抛出错误

捕获错误的目的只是记录一下,便于后续追踪.但是如果当前函数不知道如何处理这个错误,最恰当的当时继续往上抛,让顶层调用者处理.(员工处理不了的问题抛个老板,老板处理不了抛个CEO).这是很常见的一个做法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def foo(s):
n = int(s)
if n==0:
raise ValueError('invalid value: %s' % s)
return 10 / n

def bar():
try:
foo('0')
except ValueError as e:
print('ValueError!')
raise # raise不带参数,就会把当前错误原样抛出

bar()

exceptraise一个error,还可以转换错误的类型

1
2
3
4
try: 
10 / 0
except ZeroDivisionError:
raise ValueError('input error!') # 将ZeroDivisionError转换成ValueError

调试

有时候遇到一些复杂的bug,需要知道出错时,哪些变量的值食正确的,哪些是错误的.此时就需要一套调试手段来修复.

print

最简单,最常见的手法,直接print出来

断言

用断言来代替print

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

assert: 表达式n != 0应该是true,否则则断言失败,抛出AssertionError

1
2
3
Traceback (most recent call last):
...
AssertionError: n is zero!

执行时可以用-O来关闭断言

1
2
3
4
5
$ python -O err.py
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
# 关闭后,所有assert语句都可以看成pass

logging

通过写日志的方式记录错误

1
2
3
4
5
6
7
8
9
10
11
12
13
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)

>>>
INFO:root:n = 0
Traceback (most recent call last):
File "err.py", line 8, in <module>
print(10 / n)
ZeroDivisionError: division by zero

pdb

python的调试器,让程序以单步方式运行,客户以随时查看运行状态.

1
2
3
4
# 比如有这么一个程序
s = '0'
n = int(s)
print(10 / n)

然后通过pdb启动

1
2
3
python -m pdb err.py
> /Users/michael/Github/learn-python3/samples/debug/err.py(2)<module>()
-> s = '0' # 这是pdb定位到的下一步要执行的代码

输入l来查看代码

1
2
3
4
5
(Pdb) l
1 # err.py
2 -> s = '0'
3 n = int(s)
4 print(10 / n)

输入n单步执行代码

1
2
3
4
5
6
(Pdb) n
> /Users/michael/Github/learn-python3/samples/debug/err.py(3)<module>()
-> n = int(s)
(Pdb) n
> /Users/michael/Github/learn-python3/samples/debug/err.py(4)<module>()
-> print(10 / n)

任何时候输入p <变量名>来查看变量的值

1
2
3
4
(Pdb) p s
'0'
(Pdb) p n
0

q退出

pdb.set_trace()

1
2
3
4
5
6
import pdb

s = '0'
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停
print(10 / n)

运行到pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者命令c继续

1
2
3
4
5
6
7
8
9
10
$ python err.py 
> /Users/michael/Github/learn-python3/samples/debug/err.py(7)<module>()
-> print(10 / n)
(Pdb) p n
0
(Pdb) c
Traceback (most recent call last):
File "err.py", line 7, in <module>
print(10 / n)
ZeroDivisionError: division by zero

IDE

上面的方法都是土方法,最方便的就是用IDE自带的调试功能.

以vscode为例:

设置断点: 在代码行号左侧单击,出现一个红色的圆点,表示设置了一个断点.

launch.json: vscode 自动生成一个launch.json文件,用来配置环境.用来设置调试类型,启动程序,参数等.

F5启动用调试

继续F5执行到下一个断点

单步跳过F10: 逐行执行代码,遇到函数调用时跳过函数内部

单步调试F11: 逐行执行代码,遇到函数调用时进入函数内部

跳出Shift+F11: 跳出当前函数

重新开始调试Shift+Cmd+F11

结束调试Shit+F5


python笔记-错误和测试
http://example.com/2024/07/28/python-error-n-test/
作者
Peter Pan
发布于
2024年7月28日
许可协议