错误/调试/测试
错误
捕获错误
捕获错误用到的是try...except...else...finally
的结构.
例子
Tip
- 错误是类, 所有的错误类都继承自
BaseException
except
不但捕获该类型的错误, 还把其子类都一网打尽-
可以跨越多层捕获错误
-
调用栈: 如果错误没有被捕获, 会一直向上抛, 最后被捕获
例子
定义:
执行:
-
e
中存储的是错误的描述
记录错误
如果不捕获错误, 解释器会打印出错误, 但程序被结束了. 如果捕获了错误, 可以用logging
模块让解释器也按照原来的格式打印出错误.
例子
定义:
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')
执行:
抛出错误
捕获错误其实就是捕获到对应错误类的一个对象, 因此错误是有意创建并抛出的. 内置函数会抛出很多类型的错误对象, 我们也可以编写抛出的错误对象. 要抛出错误, 首先, 定义一个错误的类, 选择好继承关系, 然后用raise
语句抛出一个错误的对象:
例子
定义:
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')
执行:
错误传播
例子
定义:
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 caught in bar()')
raise
def baz():
try:
bar()
except ValueError as e:
print('ValueError caught in baz()')
baz()
执行:
在这个函数中, 我们已经捕获到了错误, 但是又把错误通过raise
抛出去了. 这样做的目的是为了将原始异常的信息向上传播, 实现和调用栈相似的功能. 如果没有在bar()
将错误抛出, 如:
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 caught in bar()')
def baz():
try:
bar()
except ValueError as e:
print('ValueError caught in baz()')
baz()
执行:
baz()
和函数是不知道bar
函数中发生了异常的, 这样就无法实现调用栈的功能.
调试
程序能一次性写对的概率很小, 我们需要知道出错的地方, 可以通过调试解决, 一般有5种方法:
断言
可以使用assert
来判断表达式的结果是否和我们预期的相符. 如果相符, 则正常运行; 否则, 抛出AssertionError
错误.
例子
定义:
执行:
logging
模块
与assert
相比, logging
不会抛出错误, 但是会输出一些信息.
例子
定义:
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
执行:
Tip
logging
允许指定信息的级别, 有logging.DEBUG
, logging.INFO
, logging.WARNING
, logging.ERROR
几个级别, 当指定level=logging.INFO
的时候, logging.debug
就不起作用了. 同理, 指定level=logging.WARNING
的时候, logging.debug
和logging.info
就不起作用了.
pdb
模块
例子
定义:
执行:
启动之后:
- 输入
l
回车查看代码 - 输入
n
回车单步执行代码 - 输入
p [变量名]
查看变量 - 输入
q
结束调试 - 输入
c
继续运行
Tip
可以用pdb.set_trace()
方法设置断点, pdb启动后会直接到断点所在的位置.
单元测试
单元测试是用来对一个模块, 一个函数或者一个类进行正确性检验的测试公国. 对于一个已有的(或者还没有的)的函数或者类, 构造出一些输入数据, 然后验证其运行结果是否与我们预期的相同, 从而判断这个模块是否正确. 同时, 如果后续修改更新了这些代码, 只需要简单的运行一遍单元测试, 就可以知道修改是否对原有的功能造成了损坏.
有一个著名的理念为测试驱动开发, TDD. 倡导先写测试程序, 然后编码实现其功能.
编写单元测试时, 需要用到unittest
模块, 编写一个测试类, 从unittest.TestCase
继承, 由这个类产生的对象可以运行很多条件判断方法(由unittest.TestCase
继承):
[test_instance].assertEqual([var], [expected_value])
[test_instance].assertTrue([var], [expected_bool])
[test_instance].assertRaises([error_cls])
例子
mydict.py
文件:
class Dict(dict):
def __init__(self, **kw):
super().__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
test_mydict.py
文件:
import unittest
from mydict import Dict
class TestDict(unittest.TestCase):
def test_init(self):
d = Dict(a=1, b='test')
self.assertEqual(d.a, 1)
self.assertEqual(d.b, 'test')
self.assertTrue(isinstance(d, dict))
def test_key(self):
d = Dict()
d['key'] = 'value'
self.assertEqual(d.key, 'value')
def test_attr(self):
d = Dict()
d.key = 'value'
self.assertTrue('key' in d)
self.assertEqual(d['key'], 'value')
def test_keyerror(self):
d = Dict()
with self.assertRaises(KeyError):
value = d['empty']
def test_attrerror(self):
d = Dict()
with self.assertRaises(AttributeError):
value = d.empty
Tip
可以在单元测试类中编写两个特殊的setUp()
和tearDown()
方法, 这两个方法会分别在每调用一个测试方法的前后被执行. 比如可以用于在测试前启动数据库, 在测试后关闭数据库.
测试目录结构
为了易于管理和测试代码, 我们的文件和目录要遵循特定的组织方式.
例子
project/
│
├── src/
│ └── mymodule.py
│
├── tests/
│ └── test_mymodule.py
│
├── README.md
├── setup.py
└── requirements.txt
src/mymodule.py
: 存放实际代码的文件tests/test_mymodule.py
: 存放单元测试的文件
要运行单元测试, 可以在项目根目录下使用以下命令: python -m unittest discover tests
, 这会自动发现并运行tests
目录中的所有测试文件.
文档测试
doctest
为Python的一个自带模块, 它会搜索模块中看起来像是交互式会话的代码片段, 然后执行并验证结果.
例子
定义Dict
类:
class Dict(dict):
'''
Simple dict but also support access as x.y style.
>>> d1 = Dict()
>>> d1['x'] = 100
>>> d1.x
100
>>> d1.y = 200
>>> d1['y']
200
>>> d2 = Dict(a=1, b=2, c='3')
>>> d2.c
'3'
>>> d2['empty']
Traceback (most recent call last):
...
KeyError: 'empty'
>>> d2.empty
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'empty'
'''
def __init__(self, **kw):
super(Dict, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
if __name__=='__main__':
import doctest
doctest.testmod()
测试Dict
类:
什么都没有输出, 说明测试全部通过.
-
错误处理. (n.d.). Retrieved June 17, 2024, from https://www.liaoxuefeng.com/wiki/1016959663602400/1017598873256736 ↩
-
调试. (n.d.). Retrieved June 17, 2024, from https://www.liaoxuefeng.com/wiki/1016959663602400/1017602696742912 ↩
-
单元测试. (n.d.). Retrieved June 17, 2024, from https://www.liaoxuefeng.com/wiki/1016959663602400/1017604210683936 ↩
-
文档测试. (n.d.). Retrieved June 17, 2024, from https://www.liaoxuefeng.com/wiki/1016959663602400/1017605739507840 ↩