unit-test
自动化测试
不同于C和Java等语言编写的程序,Python只有在运行中才会检查一些错误,因此只有在运行和测试程序时,才会知道它是否能够正常的运行。
为了解决这个问题,就有了用于测试,调试和探查Python代码的技术和库模块。
文档字符串和doctest模块
如果函数,类,模块的第一行是一个字符串,那么这个字符串就是文档字符串,当调用help()时,这些文档将会展示出来。
由于程序员倾向于在交互式shell中进行试验时查看文档字符串,所以这些字符串中通常会包含简短的交互式例子
1 | def split(line, types=None, delimiter=None): |
上面这个demo中,封装了一下Python的split函数,使其可以直接在切完后进行类型转换,就写了一个文档来告诉看这个代码或者使用help的程序员
但这样存在一个问题,如果忘记更改这个文档字符串了怎么办?可以使用doctest模块来解决这个问题。我们可以新建一个.py文件来测试它,或者直接测试本身
1 | # 新开文件测试 |
doctest要求函数输出与从交互解释器得到的输出完全一致,所以需要特别重视精度问题
单元测试和unittest模块
对于更全面的程序测试,可以使用unittest模块来进行测试。如果进行单元测试,开发人员会为程序每个组成元素(函数,类,方法,模块)编写独立的测试案例。然后运行这些测试来组成更大的测试框架和工具
我们试着编写单元测试来测试我们封装的split()函数
1 | import unittest |
运行时完毕得到了这样的结果,说明样例的测试成功
Ran 3 tests in 0.003s
OK
unittest的基本使用包括定义一个继承自unittest.TestCase的类,这个类中,各种测试由以名称test开头的方法定义,如上面的函数(可以随意命名,但必须是以test来开头),在各个测试中,使用断言来检查不同的条件
经常使用的是unittest.TestCase的实例t的以下方法
1 | # 在运行任何测试方法前, 调用它来执行设置步骤 |
调优与优化
进行计时测量
如果只是想要对长时间运行的Python程序进行计时,最简单的方法通常是在UNIX time等命令的控制下运行它。如果需要对一组长时间运行的语句进行计时,可以插入time.clock()
的调用来获取CPU时间的最新读数,或者插入time.time()的调用来读取最新的时钟时间
当然如果仅仅想对一个特定的语句进行测试,可以使用timeit模块的timeit(code [, setup])函数
如:
1 | >>> from timeit import timeit |
setup语句用于设定环境,该函数会报告执行时间(使用默认执行次数),当然使用number=count参数也可以自己指定测试的重复次数
进行内存测量
sys模块有getsizeof()函数,用于分析各个Python对象内存的占用(以字节为单位)
1 | >>> import sys |
-
对于列表和元组,字典等容器,报告的大小只是容器对象本身的大小,而不是容器中包含的所有对象的累计大小。
-
getsizeof()只是粗略的计算内存使用量,而在内部,解释器会通过引用计数来频繁的共享对象,所以消耗的实际内存会比想象中的小。
ps:为什么int型有28个字节
参考
int 类型在python中是动态长度的。因为python3中int类型是长整型,理论支持无限大的数字,但它的结构其实也很简单, 在 longintepr.h 中定义:
1 | struct _longobject { |
这结构是什么意思呢,重点在于 ob_digit 它是一个数组指针。digit 可认为是 int 的别名。python的整型存储机制是这样的。比方要表示一个很大的数: 123456789 。而每个元素最大只能表示3位十进制数(为理解打的比方)。那么python就会这样存储:
1 | ob_digit[0] = 789 |
低位存于低索引下。python中整型结构中的数组,每个元素最大存储 15 位的二进制数(不同位数操作系统有差异32位系统存15位,64位系统是30位)。
因此,sys.getsizeof(0) 数组元素为0。此时占用24字节(PyObject_VAR_HEAD 的大小)。 sys.getsizeof(456) 需使用一个元素,因此多了4个字节。
反汇编
dis模块可以将Python函数,方法,类反汇编为低级的解释器指令。使用dis模块的dis()函数
调优策略
-
使用__slot__来限制实例内存
-
尽量避免使用(.)进行属性查找, 比较常用的是当大量使用某个库的某个函数是,from xxx import xx而非import xxx, 使用xxx.xx来访问
-
避免对常见情况使用异常,使用if来规避异常,使用异常来处理不常见的情况
-
鼓励函数式编程和迭代,多使用列表推导,生成器表达式,生成器,协程,闭包。手动迭代数据往往不如使用列表推导和生成器
-
使用装饰器和元类用于修改函数和类