分享

Python代码调试的那些“最少且必要”技巧

 长沙7喜 2020-05-13

脚本之家

你与百万开发者在一起


异常处理模块能帮助我们在运行期间处理异常信息,但Python代码还有更为基础的错误——语法错误和逻辑错误

语法错误相对简单,在解释器的帮助下,我们很快就能定位错误所在。但对逻辑错误的调试就难多了,这些语法或逻辑层面的错误,构成了各式各样的代码bug。

为了调试错误,我们需要知道,出错时哪些变量的值是正确的,哪些变量的值是错误的。因此,我们需要掌握一些代码调试的基本技巧。


本文选自《Python极简讲义:一本书入门数据分析与机器学习》一书。这是一本图文并茂、简单易读的Python极简讲义,以掌握“最少必要知识”为写作理念,对初学者十分友好!


01 利用print()输出观察变量

第一种方法,简单而有效,直接而粗暴,就是用print()把需要观察的变量打印出来,如下所示。

【例1】 print()输出观察变量(print-err.py)

01   def foo(s):
02       n = int(s)                     #字符串转换为整型
03       print('n = {}'.format(n))   #输出观察变量n的值
04       return 10 / n
05   
06   foo('0')

运行结果

n = 0
---------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
……
<ipython-input-9-d21f20e5d17e> in foo(s)
      2     n = int(s)
      3     print('n = {}'.format(n))
----> 4     return 10 / n
      5 
      6 def main():
ZeroDivisionError: division by zero

根据打印处的信息(第3行)和错误信息(division by zero),我们可以很容易地定位错误所在:代码第4行,作为分母,n值为0。

02  assert断言

用print()观察变量的不足之处在于,调试完毕后,我们还得手动将它们删掉,如果调试工作量较大,造成print()满天飞,删除大量print()语句的工作量也不容小觑。而且,如果程序中到处充斥着print()语句,输出信息也会非常繁杂,给程序员造成困扰。

因此,就有了第二种方法—断言(assert)。凡是可用print()来辅助查看的,都可以用assert来替代。它用来测试某个条件(condition)的布尔值,系统默认这个条件为真,此时断言悄然无息,我们感知不到它的存在。但是,一旦条件为假,就会触发异常。assert的语法格式如下。

assert <condition>   #第一种情况,不给出错误信息

在Python中,可以把assert理解为简化版的异常处理,它与如下语句等价。

if not <condition>    
   raise AssertionError

assert后面也可以紧跟参数,给出更为详细的错误信息,示例如下。

assert <condition> [, arguments]     #第二种情况,给出错误信息(可选项)

这种情况等价于如下语句。

if not condition:
    raise AssertionError(arguments)

下面我们通过具体示例来说明assert的用法,见【例2】。

【例2】assert的用法(assert_no_err_msg.py)

01   def avg(score):
02       assert len(score) != 0
03       return sum(score) / len(score)
04   
05   score = []
06   print('平均分数为:',avg(score))

运行结果

AssertionError                            Traceback (most recent call last)
<ipython-input-11-56d552b0cddd> in <module>
      4 
      5 score = []
----> 6 print('平均分数为:',avg(score))
<ipython-input-11-56d552b0cddd> in avg(score)
      1 def avg(score):
----> 2     assert len(score) != 0
      3     return sum(score)/len(score)
      4 
      5 score = []
AssertionError:

代码分析

由于代码的第05行是一个空列表,其长度为0,因此会让第02行的判断条件len(score) != 0 为假,这时就会触发异常,导致程序终止运行。此时,如果将第05行代码修改如下:

05   score = [90,85,78]

整个程序将能正常运行,运行结果如下。

平均分数为: 84.33333333333333

使用assert的好处在于,当判断条件为真时,用户是感觉不到assert的,因为assert只有当判断条件为假时才“刷存在感”,给出错误信息。错误信息一旦给出,在某种程度上就定位了代码的bug所在,从而达到了程序调试的目的。调试完毕后,用户无须删除assert语句。

【例2】中的assert并没有给出错误信息,可读性不强。事实上,我们还可以显式给出错误信息。我们可以如下修改【例2】的第02行代码。

assert len(marks) !0'列表为空,咋整啊!'

这里,断言条件后面的'列表为空,咋整啊!',就是条件一旦为假时输出的错误信息。我们假设,此时第05行依然为空列表,这时【例2】的运行结果如下。

AssertionError                            Traceback (most recent call last)
<ipython-input-13-a477886d663d> in <module>
      5 score = []
      6 # score = [90,85,78]
----> 7 print('平均分数为:',avg(score))
<ipython-input-13-a477886d663d> in avg(score)
      1 def avg(score):
----> 2     assert len(score) != 0,  '列表为空,咋整啊!'
      3     return sum(score) / len(score)
      4 
      5 score = []
AssertionError: 列表为空,咋整啊!

很明显,有了错误信息,就更容易找到代码的错误所在了。

如果断言太多,也会遭遇与print()类似的处境,异常信息会让我们“应接不暇”。如果不需要断言来帮忙,则在命令行启动Python解释器时可用“-O”参数来关闭assert,如下。

python -O assert_no_err_msg.py    #选项是大写的字母O,而非数字0



除了前面提到的利用print()assert进行调试,我们还可以使用IDE(如PyCharm等)进行调试,这些集成开发环境有着非常好用的“单步调试功能”,同时配合控制台的输出,也能比较便捷地定位错误。

当我们开发的项目规模比较大时,我们会发现,logging才是终极武器。logging是Python的日志模块。使用这个模块的好处在于,它允许我们指定记录信息的级别,有debug、info、warning、error等。

我们可以根据需要输出不同级别的信息。例如,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,我们就不必担心太多输出信息会冲淡关注力。

关于这个模块的知识,就留给“爱折腾”读者自学吧。高手,永远都是自学出来的!

相关图书

《Python极简讲义:一本书入门数据分析与机器学习》是一本理论结合实战,娓娓道来的入门好书。书中提供了入门数据科学领域的 极简必要知识 

不同于其他图书,本书不求全,但求精。这里的“精”是指,给出入门数据分析的“极简必要知识'( Minimal Actionable Knowledge and Experience,MAKE )。在极短的时间内,掌握数据分析的MAKE之道,这是本书的一大特色。

本书脉络

大咖推荐

  • 董付国,知名Python讲者、16本Python系列图书作者

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多