测试Python代码的4个简单的库

欢迎关注 “小白玩转Python”,发现更多 “有趣”

引言

测试代码的速度和效率是软件开发的一个重要方面。当代码占用太长时间或者消耗太多资源(如内存或 CPU)时,可能会很快遇到各种问题,例如:代码运行的机器可能变得不稳定,在某些情况下甚至会丢失数据。确保在出现明显的性能问题时进行检查是有帮助的,但是建立性能基准和概要也同样重要。

在开发过程中,应该对代码从开始到结束的功能进行测试,但是对性能进行测试也很重要。在编写代码时养成测试代码的好习惯,比如速度和资源利用率,这会让你在编写代码的过程中省去很多麻烦。

在本文中,我们将探索可以对 Python 代码进行基准测试和基线化的方法。我们将要介绍的库是免费提供的,并且提供了灵活的方法来处理性能计时、资源消耗度量等等。让我们开始吧。

1.计时

首先是一个 Python 实用程序,它被广泛应用于性能测试。让我们设置一个简单的测试脚本并使用 timeit,来进行一个简单的时间测试:

#!/usr/bin/env python3
# test.py
import timeit
import time
def long_function():
    print('function start')
    time.sleep(5)
    print('function end')
print(timeit.Timer(long_function).timeit(number=2))

在我们的 long_function 内部,我们使用 time.sleep 引入了一些延迟,以便模拟一些长时间运行的任务。接下来,为了实际测试我们的功能,我们将其传递给timeit.Timer。Timer 类测量函数的整体执行速度。number 参数指定应重复测试多少次。如果你的函数执行时间可能略有不同,这将很有用。重复进行测试可以更好地了解速度,因为你将拥有更多可处理的数据。

运行上面的代码后,我们可以获得以下输出。这表明 timeit 运行了两次函数,然后输出了所花费的总时间。由于我们只是在调用 sleep,所以差异不会太大:

function start
function end
function start
function end
10.00957689608913

Timeit 库非常适合对代码片段执行快速、独立的测试,但也可以作为独立单元从命令行运行,并包含更多有用的参数。

2.行剖析器

我们将探索的下一个库名为 line_profiler,它的用法比其他解决方案更加独特。line_profiler 库允许你获取文件中每一行的执行时间。看到每一行所花费的时间,就可以快速地查明问题,而不是一行一行地查找密集的代码。

一开始,line_profiler 的标准用法似乎有点混乱,但是一旦你使用了几次,它就变得更容易了。为了对代码进行分析,需要在每个函数中添加@profile装饰器。让我们重新使用之前的例子并对其进行调整,看看它是如何工作的:

#!/usr/bin/env python3
# test.py
import time
@profile
def long_function():
    print('function start')
    time.sleep(5)
    print('function end')
long_function()

看起来很简单,对吧?这是因为使用 line_profiler,你将不必导入任何内容或大量更改代码,只需添加装饰器即可。因为我们已经设置了装饰器,所以剩下的唯一要做的事情就是测试代码。为了运行 line_profiler,必须做另外两件事:

kernprof -l test.py
python -m line_profiler test.py.lprof

上面的第一个命令将在文件上实际运行 line_profiler 并在同一目录中生成一个单独的 .lprof 文件。.lprof 文件包含结果,使用第二个命令中的模块本身可以生成报告。让我们看一下第二个命令的输出:

Timer unit: 1e-06 s
Total time: 5.00472 s
File: test.py
Function: long_function at line 6
Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     6                                           @profile
     7                                           def long_function():
     8         1         15.0     15.0      0.0      print('function start')
     9         1    5004679.0 5004679.0    100.0      time.sleep(5)
    10         1         21.0     21.0      0.0      print('function end')

概要分析功能的每一行均列出了其详细统计信息。因为我们在 long_function 的 sleep 中花费了很多时间,所以它几乎占用了文件执行时间的100%。使用 line_profiler 生成所有执行时间去向的分解,让你快速确定可能需要专注于重构的位置。

3.资源

对于接下来的两个库,我们将重点关注运行 Python 代码所涉及的底层资源。在现代环境中,能够告诉代码使用了多少 CPU 和内存几乎是必需的。这个库允许你测量代码中的资源使用情况,甚至可以设置特定资源的使用限制。

让我们来看一个如何从脚本中检查 CPU 使用情况的例子:

#!/usr/bin/env python3
# test.py
import time
from resource import getrusage, RUSAGE_SELF
def long_function():
    for i in range(10 ** 10):
        2 + 2
long_function()
print(getrusage(RUSAGE_SELF))

在此示例中,为了在 long_function 期间给 CPU 带来更大的负担,我们在较大范围的数字上循环并强制 CPU 执行一些计算。这将产生比 sleep 更高的负载。

完成上述测试之后,我们应该能够看到下面的使用输出:

resource.struct_rusage(ru_utime=152.395004, ru_stime=0.035994, ru_maxrss=8536, ru_ixrss=0, ru_idrss=0, ru_isrss=0, ru_minflt=1092, ru_majflt=0, ru_nswap=0, ru_inblock=0, ru_oublock=0, ru_msgsnd=0, ru_msgrcv=0, ru_nsignals=0, ru_nvcsw=0, ru_nivcsw=1604)

在这个输出中可以看到,我们花费了大量的 CPU 周期。查看 ru_utime (用户时间) ,它显示代码总共花费了152秒的时间。当然我们不仅可以测量 CPU 时间,还可以通过其他一些指标了解块 IO 和堆栈内存使用情况。

4.内存分析器

Memory_profiler 库类似于 line_profiler,但侧重于生成与内存使用直接相关的统计数据。在代码上运行 memory_profiler 时,仍然会得到逐行分解,但是将重点放在整体内存使用和逐行增量内存使用上。

就像在 line_profiler 中一样,我们将使用相同的装饰器结构来测试我们的代码。这是修改后的示例代码:

#!/usr/bin/env python3
# test.py
@profile
def long_function():
    data = []
    for i in range(100000):
        data.append(i)
    return data
long_function()

在上面的示例中,我们创建了一个测试列表并将大量整数推入其中。这会使列表缓慢增加,因此我们可以看到内存使用量随时间增长。为了查看 memory_profiler 报告,我们可以简单地运行:

python -m memory_profiler test.py

这将产生以下报告,其中包含逐行内存统计信息:

Filename: tat.py
Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
     3   38.207 MiB   38.207 MiB           1   @profile
     4                                         def long_function():
     5   38.207 MiB    0.000 MiB           1       data = []
     6   41.934 MiB    2.695 MiB      100001       for i in range(100000):
     7   41.934 MiB    1.031 MiB      100000           data.append(i)
     8   41.934 MiB    0.000 MiB           1       return data

如上所示,我们的函数从大约38 MB 的内存使用开始,在我们的列表填满之后增长到41.9 MB。虽然我们可以从 resource 库中获得内存使用情况信息,但是它不会像 memory_profiler 那样产生详细的逐行分解。如果你正在查找内存泄漏或处理一个特别臃肿的应用程序,这将是一个非常好的方法。

·  END  ·

HAPPY LIFE

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值