测试开发之Python核心笔记(17): 调试程序

17.1 为什么要调试

  • 代码本身有问题,需要我们找到 root cause 并修复
  • 代码效率有问题,比如过度浪费资源,latency很大,因此需要我们 debug
  • 在开发新的 feature 时,一般都需要测试
  • 阅读开源项目时,通过调试了解处理逻辑

17.2 调试方法

17.2.1 print调试法

对于简单的程序,使用print函数输出可以进行调试。但是对于大型程序,怀疑出错的地方可能很多,就可能代码中需要写大量的print函数。调试完毕后,还要再删除掉。显然比较麻烦。

17.2.2 Pycharm断点

Pycharm这个好用的Python集成开发环境,提供断点调试功能。在怀疑出错的地方添加断点,再通过Debug方法执行程序,程序会停止在断点处,相比使用print函数,大大提高了效率。

添加断点的方法是在Pycharm行号右侧,鼠标左键点击一下就可以了,这样程序通过Debug执行到这里就会停住。还可以给断点设置条件,就是当满足一定条件才停住,方法是在红色断点圆点上点击右键,在Condition栏里面添加条件,再点击Done之后,发现圆点右下角有一个问号了,表示这是一个条件断点。
在这里插入图片描述
在Pycharm上点击Debug按钮,可以开始调试程序,会在Pycharm下显示Debugger的调试窗口。这个窗口有三个主要部分组成:最左边是调用栈信息Frames,中间是Varibles信息,最右边是Watches信息,这个窗口可以将我们感兴趣的Varibles通过+号添加进来。
在这里插入图片描述
当程序停住后,可以通过Varibles窗口上方的一排按钮控制程序的执行。
在这里插入图片描述
最左边的,表示让程序继续执行一行,左边第二个是进入当前行中的函数内不,从右边数第二个是从函数中跳出到被调用处的位置,右数第一个是执行到光标所在行。当通过这些按钮控制程序执行时,Frames窗口,Varibles窗口,Watches窗口信息会发生变化。我们就是通过这些信息来了解代码的执行中间过程的。

17.2.3 pdb调试法

pdb 是 Python 程序自带的一种用于交互式调试的包。需要在程序中,加入“import pdb”和“pdb.set_trace()”这两行代码。pdb一共有几个常见的命令:p、n、l、s,可以结合下面的代码学习:

import pdb

a = 1
b = 2
pdb.set_trace()  # 暂停了下来,等待用户输入


def echo(x):
    print(x)


c = 3
echo(a + b + c)

执行上面的代码,我分别使用上面的四个命令,请看注释。

/usr/local/bin/python3.7 /Users/chunming.liu/PycharmProjects/mypytest/test/test_suite1/func.py
> /Users/chunming.liu/PycharmProjects/mypytest/test/test_suite1/func.py(8)<module>()
-> def echo(x):    # 运行到这一行停止
(Pdb) p a  # 打印变量a
1
(Pdb) n  # 执行下一行
> /Users/chunming.liu/PycharmProjects/mypytest/test/test_suite1/func.py(12)<module>()
-> c = 3
(Pdb) l  # 列出当前代码行前后11行代码
  7  	
  8  	def echo(x):
  9  	    print(x)
 10  	
 11  	
 12  ->	c = 3
 13  	echo(a + b + c)
[EOF]
(Pdb) n  # 执行下一行
> /Users/chunming.liu/PycharmProjects/mypytest/test/test_suite1/func.py(13)<module>()
-> echo(a + b + c)
(Pdb) s  # 进入到echo函数中
--Call--
> /Users/chunming.liu/PycharmProjects/mypytest/test/test_suite1/func.py(8)echo()
-> def echo(x):
(Pdb) p x
6
(Pdb) n
> /Users/chunming.liu/PycharmProjects/mypytest/test/test_suite1/func.py(9)echo()
-> print(x)
(Pdb) n
6
--Return--
> /Users/chunming.liu/PycharmProjects/mypytest/test/test_suite1/func.py(9)echo()->None
-> print(x)
(Pdb) 

使用命令s进入了函数 echo() 的内部,显示–Call–;而当我们执行完函数 echo() 内部语句并跳出后,显示–Return–。另外,还可以使用b 11在第11行设置断点,使用c运行到下一个断点。

17.3 性能分析方法

cProfile 提供了每个代码块执行效率的详细分析。

cProfile 计算出每个模块消耗的时间,这样就可以知道程序的瓶颈所在,从而对其进行修正或优化。

下面是一个输出Fibonacci序列的函数,用cProfile分析性能:

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)')  # 分析哪个函数,就将那个函数作为cProfile.run的参数

将会输出,性能分析数据:

7049218 function calls (96 primitive calls) in 2.519 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    2.519    2.519 <string>:1(<module>)
     31/1    0.000    0.000    2.519    2.519 func.py:13(fib_seq)
7049123/31    2.519    0.000    2.519    0.081 func.py:4(fib)
        1    0.000    0.000    2.519    2.519 {
    
    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}

简单介绍一下性能分析的数据:

扫描二维码关注公众号,回复: 13822557 查看本文章
  • ncalls,是指相应代码 / 函数被调用的次数;
  • tottime,是指对应代码 / 函数总共执行所需要的时间(注意,并不包括它调用的其他代码 / 函数的执行时间);
  • tottime percall,就是上述两者相除的结果,也就是tottime / ncalls;
  • cumtime,则是指对应代码 / 函数总共执行所需要的时间,这里包括了它调用的其他代码 / 函数的执行时间;
  • cumtime percall,则是 cumtime 和 ncalls 相除的平均结果。

这段程序执行效率的瓶颈,在于第二行的函数 fib(),它被调用了 700 多万次。

程序中有很多对 fib() 的调用,其实是有很多重复的,那我们就可以用字典来保存计算过的结果,防止重复计算。利用装饰器优化上面的代码:

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)

这次的性能输出如下,可见fib函数的调用次数只剩下31次了,大大提高了代码执行效率:

215 function calls (127 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 <string>:1(<module>)
       31    0.000    0.000    0.000    0.000 func.py:15(fib)
     31/1    0.000    0.000    0.000    0.000 func.py:25(fib_seq)
    89/31    0.000    0.000    0.000    0.000 func.py:7(helper)
        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}

猜你喜欢

转载自blog.csdn.net/liuchunming033/article/details/107896897