首页 > Python笔记 阅读数:20

Python assert和print()代码调试详解

异常处理模块能帮助我们在运行期间处理异常信息,但 Python 代码还有更为基础的错误—语法错误和逻辑错误。语法错误相对简单,在解释器的帮助下,我们很快就能定位错误所在。但对逻辑错误的调试就难多了,几乎“引无数 coder 竞折腰”!

这些语法或逻辑层面的错误,构成了各式各样的代码 bug(代码缺陷)。为了调试错误,我们需要知道,出错时哪些变量的值是正确的,哪些变量的值是错误的。因此,我们需要掌握一些代码调试的基本技巧。

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

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

【例 1】利用 print( ) 输出观察变量(print-err.py)
def foo(s):
    n = int(s)  #字符串转换为整型
    print('n = {}'.format(n)) #输出观察变量n的值
    return 10/n
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。

Python 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)
def avg(score):
    assert len(score) != 0
    return sum(score)/len(score)

score =[]
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 行代码修改如下:
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 就不起作用了。这样一来,我们就不必担心太多输出信息会冲淡关注力。

相关文章