逐行剖析Python代码

这篇文章解释了某人如何逐行剖析Python代码并获得关于代码执行的有用信息。

首先,我们将简要地介绍剖析;然后,我们将讨论什么时候逐行剖析比函数基础剖析更好用。之后,我们将讨论 Python 中的剖析实现。

什么是剖析

剖析是检查我们代码的不同部分所利用的资源的过程。对于高效的编码实践,我们通常主要关注时间复杂度(不同编码单元/函数所花费的时间)或内存复杂度(不同编码单元/函数的内存利用率)。

前者可以帮助我们专注于减少程序所花费的时间,后者则可以帮助我们优化内存的使用。

函数剖析

在任何语言中,函数级剖析主要是用来找出程序中不同函数所消耗的时间。因此,Python默认包已经包含了用于函数分析的库cProfileProfile

行剖析器

基于函数的剖析器给出了不同函数所消耗的时间的信息。然而,我们有时需要基于行的时间复杂度来检查哪一行对函数或程序的整体时间贡献更大。

在这种情况下,我们有Python中的line_profiler 库。

使用line_profiler 模块对 Python 代码进行剖析

LineProfiler 模块允许我们对代码进行逐行或逐个函数的分析。

在下面的Python代码片段中,我们创建了两个名为slow_avg_producer()fast_average_producer() 的函数。这两个函数产生相同的平均值,但它们的执行时间是不同的。

首先,安装以下模块,使用行剖析器对代码进行剖析。

!pip install line_profiler
import time
import random
def slow_avg_producer():
    time.sleep(6)  # Do nothing for 5 time units
    # generate random numbers array
    arr = [random.randint(1,100) for i in range(10000)] # generate random numbers array
    return sum(arr) / len(arr)
def fast_average_producer():
    time.sleep(1) # Do nothing for 1 time units
    # generate random numbers array
    arr = [random.randint(1,100) for i in range(10000)]
    return sum(arr) / len(arr)
def main_func():
    average = slow_avg_producer()
    print(average)
    result = fast_average_producer()
    print(average)

为了获得时间统计,我们首先创建LineProfiler 对象,然后通过它获得main_func() 的包装。最后,我们可以得到剖析的统计数据。

from line_profiler import LineProfiler
line_prof = LineProfiler()
line_prof_wrapper = line_prof(main_func)
line_prof_wrapper()
# print the profiling details
line_prof.print_stats()

上述代码产生的输出如下。时间单位是微秒。

Timer unit: 1e-06 s
Total time: 7.10521 s
File: <ipython-input-14-67ae2a9633ee>
Function: main_func at line 17
Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    17                                           def main_func():
    18         1    6054801.0 6054801.0     85.2      average = slow_avg_producer()
    19         1        676.0    676.0      0.0      print(average)
    20
    21         1    1049070.0 1049070.0     14.8      result = fast_average_producer()
    22         1        659.0    659.0      0.0      print(average)

统计结果显示,在主函数中,slow_avg_producer() 需要6.054秒,下一个print 语句需要0.000676秒。另一方面,fast_average_producer() 需要的时间非常少。

通过命令行对Python代码进行剖析

我们可以使用命令行界面来获取函数的逐行剖析细节。对于这种方法,我们使用kernprof 命令。

我们在这里也将使用同样的函数,只是我们将在每个函数的开头添加一个装饰器,@profile 。我们将这个脚本命名为average_producer.py

import time
import random
@profile
def slow_avg_producer():
    time.sleep(6)  # Do nothing for 5 time units
    # generate random numbers array
    arr = [random.randint(1,100) for i in range(10000)] # generate random numbers array
    return sum(arr) / len(arr)
@profile
def fast_average_producer():
    time.sleep(1) # Do nothing for 1 time units
    # generate random numbers array
    arr = [random.randint(1,100) for i in range(10000)]
    return sum(arr) / len(arr)
@profile
def main_func():
    average = slow_avg_producer()
    print(average)
    result = fast_average_producer()
    print(average)
main_func()

使用kernprof 命令,我们需要使用以下命令安装line-profiler 模块。

pip install line-profiler

对于Colab或Jupyter Notebook终端,在上述命令的开头使用bang符号(!)。

逐行剖析Python代码

安装后,你必须改变安装kernprof.exe 的目录。然后运行下面的命令。

kernprof 给我们提供了做 的选项,但我们在这里使用行分析器。为此,要使用 参数。cProfile -l

kernprof -l average_producer.py

逐行剖析Python代码

执行下面的命令来查看剖析结果。

python -m line_profiler average_producer.py.lprof

它在命令行界面上打印出剖析结果,如下图所示。

逐行剖析Python代码

逐行剖析Python代码