【摘要】
这是自动化运维新手村里最重要的一篇番外,没有之一。
调试应该是所有编程语言中都需要用到的一种技巧,不管对于编程新手还是资深码农,调试都会是不可或缺的技能。
刚接触编程的朋友经常会的困惑就是,已经了解某段代码的功能逻辑,但却对于逻辑具体的实现却充满疑惑,其实这是十分正常的现象。
【看不懂代码?】
最本质的原因是抽象到具体之间存在一定的代沟,功能逻辑其实就是对某个场景或需求,依照我们的编程思路抽象出完成该功能的过程,功能逻辑是通用的,可以用任何语言去实现这个功能,但不同语言之前的实现方式就会有所区分甚至大相径庭。
Python这门语言其实已经是对于新手来说相对友好的语言,功能逻辑和代码的具体实现之间不会有太大差异,甚至有的代码阅读起来就像英文一样,清晰易懂。但编程语言总归是面向计算机的,而不是面向人脑的,计算机处理代码的方式以及很多时候为了程序健壮性所作出的边界处理都会让刚接触编程的朋友较难理解,除此之外还包括每个人编程风格的不同,也会造成代码阅读的困难。
所以对于刚开始看不懂代码的朋友来说,调试是一个十分好用的方法,今天这篇文章就给大家详细介绍一下代码调试,帮大家排除编程路上看不懂代码的这个最大的障碍
【什么是调试】
调试通俗的讲就是将程序解剖开然后呈现在我们面前,解剖的方式也有很多种:
-
将整个代码拆解成一个个代码块,分别去执行,以此来了解代码中不同阶段不同步骤所执行的结果。 -
在代码中想要观察的地方插入打印输出语句,这样当代码运行起来后就可以观察到此处的具体内容。 -
使用断点调试,在想要观察的地方加入断点,让程序执行到此处时中断,这时就可以仔细分析断点处的变量情况以及查看上下文信息。
【调试的作用】
调试的作用一般分为两种:
- 排查bug。
当程序运行出现错误,并且通过错误日志无法判断出异常原因时,可以尝试重现bug,并通过上面几种调试的手段来观察程序的执行,以此来判断真正的异常原因
- 分析代码。
如果已经了解了代码的大致逻辑,但对于代码的细节处理仍有疑惑,或者某些使用循环逻辑的地方较难直接用头脑分析演算出执行过程,这时候就可以借助调试手段来更为详尽准确的分析代码。
【如何调试】
上文的三种调试方法中,前两种都十分简单,我觉得即使对于刚接触编程的朋友来说应该也都可以自己尝试使用。今天我们着重讲解一下第三点:断点调试,这里主要是达到分析代码的作用,至于排查bug我们后续会详细讲解。
任何语言编程语言,不管是Java,C++,Golang还是Python,都可以借助工具实现断点调试,对于Python来说一般主流的代码编辑器都具备断点调试的功能,比如VSCode或者Pycharm。
但今天先不讲解如何通过IDE(Integrated Development Environment,集成开发环境)自带的功能做调试,而是教给大家一个Python里面自带的代码调试方法,我觉得这种方式是最为通用的,即使在没有IDE的服务器上也一样可以进行代码调试。
【PDB】
pdb 是Python内置的一个方法库,为 Python 程序提供了一种交互的源代码调试功能,主要特性包括设置断点、单步调试、进入函数调试、查看当前代码、查看栈片段、动态改变变量的值等。
这里举一个简单的例子:
import sys
def foo(num):
ret = []
for i in range(num):
tmp = i ** 2
ret.append(tmp)
return ret
if __name__ == "__main__":
args = sys.argv
result = foo(int(args[1]))
print(args)
如上代码是一个获取从零到某个指定数字的平方序列的函数,通过命令行传入指定数字,然后返回它的平方序列。
我相信通过上面的解释大家都可以理解这个程序的功能逻辑,但仔细看代码实现时,大家可以会有困惑,我通过sys.argv 获取到的究竟是什么,是什么类型,包含了哪些值,以及我在foo() 函数的循环中每次tmp 值是什么。
当然这些问题其实应该属于Python的基础知识,理应熟记并且掌握,但对于刚接触编程的朋友来说某些基础知识可能记得不牢固,或者有的代码细节略微晦涩难懂,这时候就可以通过pdb 这一利器来解剖代码。
引入pdb 并在某一行插入断点:
import sys
import pdb
def foo(num):
ret = []
for i in range(num):
tmp = i ** 2
ret.append(tmp)
return ret
if __name__ == "__main__":
args = sys.argv
pdb.set_trace()
result = foo(int(args[1]))
print(args)
在命令行中执行python my_square 10 会得到如下输出
这里表示程序在result = foo(args[1]) 处中断了,并且开头的(Pdb) 表示已经进入到了pdb 交互模式,我们这时候可以输出一些pdb 的指令进行调试。
l (小写的L)表示查看上下文代码,结果如下:
可以查看到断点处的上下文代码,清晰的看到当前断点所处的位置,以此来去对上下文做出更进一步的调试操作。
p [var_name] 使用p字母加变量名查看变量,结果如下:
这里可以看到args 是一个数组类型,并且第一个元素是程序代码的文件名,第二个元素才是输入的参数,所以这也是为什么我们调用函数时传入的args[1] 。
这里有一个需要注意的点就是p字母在某些时候可以省略,直接输入变量名也可以查看变量详情,但当变量名与pdb 内置的关键字冲突时就无法生效,所以建议大家平时也都使用p + var_name 的方式查看变量。
s 使用s字母可以执行下一步并且步进到函数中去,结果如下:
由之前的断点位置可知下一步该调用foo() 函数,如果想观察到foo() 函数内部的具体实现,就可以使用s字母,步进到函数体中,由上图也可以看出,输入s后已经位于了foo() 函数的定义处,这时候就已经可以使用p num 查看函数的参数详情了。
这里需要注意的是,如果想直接观察foo() 函数,也可以在foo() 函数内部写pdb.set_trace() 并不是一定要从最外层步进才可以。
n 使用n字母同样可以执行下一步,结果如下:
输入字母n之后可以让程序执行下一步,如上图我们输入n之后,标识程序已经执行到了下一行,这里可以结合p p [var_name] 一起使用,观察当前断点处的上下文变量信息。
r 使用r字母同样可以执行下一步,但如果当前位于函数内,则会直接执行到函数结束处
如上图,输入r后直接跳转到了foo() 函数的最后一行。
-
c 使用c字母可以继续执行程序。通常在观察到想要观察的信息后,会直接输入c执行完程序的剩余内容。 -
break line_number 使用break+行号可以设置断点,如下图:
输入break可以在当前行设置断点,如果break后加行号则可以在指定行号设置断点,如上图,在第八行有一个B-> 的符号,表示在第八行处有断点。
当通过break设置断点后,无论是输入 r 还是输入 c 都会在断点处中断,所以还需要通过 clear 清除断点,使用clear [line_number] 可以清除指定行号处的断点。
-
q(uit) 使用q字母可以直接退出调试模式 -
有两点需要注意的是:
pdb还有很多其他的用法,但我们先学习这些较为常用的即可。
【总结】
代码调试虽然是一个利器,但仍需要合理正确的使用它,任何时候都不应该无脑的使用调试,而是应该先尝试理解分析代码的逻辑,这样才能事倍功半。
欢迎大家添加我的个人公众号【Python玩转自动化运维】加入读者交流群,获取更多干货内容
|