前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python自带的调试及性能分析神器

Python自带的调试及性能分析神器

作者头像
somenzz
发布2020-11-25 14:30:35
2K0
发布2020-11-25 14:30:35
举报
文章被收录于专栏:Python七号Python七号

阅读本文大概需要 5 分钟。

工具可以大大提升效率,节省时间,能自己掌控的时间越多,越自由。Python 也是效率工具,使用的越多,你省下来的时间就越多,你就会越觉得自由,因为一切尽在你的代码掌控中。

但有一种情况,可能会耗费你很多时间,那就是调试和性能分析,说到这里,有人可能会说,调试不就是 print 下变量,性能分析不就是加个耗时统计么,有多耗费时间呢?没错,简单的程序,怎么弄都是简单的,如果是复杂的程序,比如上千行的代码,无限多的调用,你还用简单的方法,我只能说你很有耐心。

今天为大家分享下 Python 标准库自带神器,一个是调试工具 pdb,一个是性能分析工具 cProfile,非常实用,如果不会这两个,真的太遗憾了。

使用 pdb 调试

先说下为什么用 pdb,假如你只会用 pycharm 或 vscode 的调试(debug)功能,现在让你直接在服务器对异常进行调试,没有任何图形界面的 IDE,只有 Python 环境及运行的代码,你怎么办?

此外,不少代码已经挪到了类似 Jupyter 的 Notebook 中,往往就要求开发者使用命令行的形式,来对代码进行调试。

所以,掌握通用技术才能通吃,而命令行的调试工具 pdb 就是通用的,掌握这个,无论什么环境都不影响你 debug。

接下来,我们就一起来看看,pdb 在 Python 中到底应该如何使用。首先,要启动 pdb 调试,我们只需要在程序中,加入“import pdb”和“pdb.set_trace()”这两行代码就行了,比如下面这个简单的例子:

代码语言:javascript
复制
import pdb
for i in range(10000):
    print(i)
    if i == 800:
        pdb.set_trace()

当这个循环进行到 i==800 时,自动停下来进入命令行的调试,输入 i 即可查询变量的值,输入 n 表示执行下一行,输入 ll 查看上下文,输入 help 查看帮助。

代码语言:javascript
复制
......
799
800
> /Users/aaronbrant/test.py(3)<module>()
-> for i in range(10000):
(Pdb) i
800
(Pdb) n
> /Users/aaronbrant/test.py(4)<module>()
-> print(i)
(Pdb) n
801
> /Users/aaronbrant/test.py(5)<module>()
-> if i == 800:
(Pdb) ll
  1      import pdb
  2
  3      for i in range(10000):
  4          print(i)
  5  ->        if i == 800:
  6              pdb.set_trace()
(Pdb) help

Documented commands (type help <topic>):
========================================
EOF    c          d        h         list      q        rv       undisplay
a      cl         debug    help      ll        quit     s        unt
alias  clear      disable  ignore    longlist  r        source   until
args   commands   display  interact  n         restart  step     up
b      condition  down     j         next      return   tbreak   w
break  cont       enable   jump      p         retval   u        whatis
bt     continue   exit     l         pp        run      unalias  where

如果使用 IDE,是否要点击 800 次呢,我不是很清楚,没试过,如果使用 IDE 来断点定位至循环内的 800 次,我会直接放弃,选用其他方式。

其他命令:

  • s 表示 step into,即进入相对应的代码内部。这时,命令行中会显示”--Call--“的字样,当你执行完内部的代码块后,命令行中则会出现”--Return--“的字样。
  • r 表示 step out,即继续执行,直到当前的函数完成返回。
  • b 可以用来设置断点。比方说,我想要在代码中的第 10 行,再加一个断点,那么在 pdb 模式下输入”b 11“即可。
  • c 则表示一直执行程序,直到遇到下一个断点。

当然,除了这些常用命令,还有许多其他的命令可以使用,这里我就不在一一赘述了。你可以参考对应的官方文档(https://docs.python.org/3/library/pdb.html#module-pdb),来熟悉这些用法。

使用 cProfile 进行性能分析

除了要对程序进行调试,性能分析也是每个开发者的必备技能。日常工作中,我们常常会遇到这样的问题:在线上,我发现产品的某个功能模块效率低下,延迟(latency)高,占用的资源多,但却不知道是哪里出了问题。这时,对代码进行 profile 就显得异常重要了。

这里所谓的 profile,是指对代码的每个部分进行动态的分析,比如准确计算出每个模块消耗的时间等。这样你就可以知道程序的瓶颈所在,从而对其进行修正或优化。当然,这并不需要你花费特别大的力气,在 Python 中,这些需求用 cProfile 就可以实现。

举个例子,比如我想计算斐波拉契数列,运用递归思想,我们很容易就能写出下面这样的代码

代码语言:javascript
复制
def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

def fib_seq(n):
    res = []
    if n > 0:
        res.extend(fib_seq(n-1))
    res.append(fib(n))
    return res

print(fib_seq(30))

接下来,我想要测试一下这段代码总的效率以及各个部分的效率。那么,我就只需在开头导入 cProfile 这个模块,并且在最后运行 cProfile.run() 就可以了,如下所示:

代码语言:javascript
复制
import cProfile
def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

def fib_seq(n):
    res = []
    if n > 0:
        res.extend(fib_seq(n-1))
    res.append(fib(n))
    return res

cProfile.run('fib_seq(30)')

或者更简单一些,直接在运行脚本的命令中,加入选项“-m cProfile”也很方便:

代码语言:javascript
复制
python -m cProfile test.py

运行结果如下:

代码语言:javascript
复制
(py37env) ➜  ~ python test.py
         7049218 function calls (96 primitive calls) in 1.503 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.503    1.503 <string>:1(<module>)
     31/1    0.000    0.000    1.503    1.503 test.py:10(fib_seq)
7049123/31    1.503    0.000    1.503    0.048 test.py:2(fib)
        1    0.000    0.000    1.503    1.503 {built-in method builtins.exec}
       31    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
       30    0.000    0.000    0.000    0.000 {method 'extend' of 'list' objects}

这里有一些参数你可能比较陌生,我来简单介绍一下:

  • ncalls,是指相应代码 / 函数被调用的次数;
  • tottime,是指对应代码 / 函数总共执行所需要的时间(注意,并不包括它调用的其他代码 / 函数的执行时间);
  • tottime percall,就是上述两者相除的结果,也就是tottime / ncalls;
  • cumtime,则是指对应代码 / 函数总共执行所需要的时间,这里包括了它调用的其他代码 / 函数的执行时间;
  • cumtime percall,则是 cumtime 和 ncalls 相除的平均结果。

了解这些参数后,再来看运行结果。我们可以清晰地看到,这段程序执行效率的瓶颈,在于第二行的函数 fib(),它被调用了 700 多万次。

有没有什么办法可以提高改进呢?答案是肯定的。通过观察,我们发现,程序中有很多对 fib() 的调用,其实是重复的,那我们就可以用字典来保存计算过的结果,防止重复。改进后的代码如下所示:

代码语言:javascript
复制
def memoize(f):
    memo = {}
    def helper(x):
        if x not in memo:
            memo[x] = f(x)
        return memo[x]
    return helper

@memoize
def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)


def fib_seq(n):
    res = []
    if n > 0:
        res.extend(fib_seq(n-1))
    res.append(fib(n))
    return res

fib_seq(30)

上述代码保存为 test2.py,直接在命令行执行,结果如下:

代码语言:javascript
复制
(py37env) ➜  ~ python -m cProfile test2.py
         216 function calls (128 primitive calls) in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 test2.py:1(<module>)
        1    0.000    0.000    0.000    0.000 test2.py:1(memoize)
     31/1    0.000    0.000    0.000    0.000 test2.py:19(fib_seq)
    89/31    0.000    0.000    0.000    0.000 test2.py:3(helper)
       31    0.000    0.000    0.000    0.000 test2.py:9(fib)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
       31    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
       30    0.000    0.000    0.000    0.000 {method 'extend' of 'list' objects}

可以看出效率得到了极大的提升。

这只是个简单的例子,便是 cProfile 的基本用法,当然,cProfile 还有很多其他功能,还可以结合 stats 类来使用,你可以阅读相应的官方文档来了解。

小结

孰能生巧,pdb 是 Python 常用的调试工具,cProfile 是经典的性能分析工具,在 debug 及性能分析方面,可以大大提升你的效率。文章内容整理自极客时间专栏,如果觉得对你有帮助,希望系统的学习 Python 技术,欢迎扫下方二维码订阅,并加我微信好友,可以得到返现哦。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python七号 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用 pdb 调试
  • 使用 cProfile 进行性能分析
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档