>>> from env_helper import info; info()
页面更新时间: 2022-03-23 10:03:43
运行环境:
    Linux发行版本: Debian GNU/Linux 11 (bullseye)
    操作系统内核: Linux-5.10.0-11-amd64-x86_64-with-glibc2.31
    Python版本: 3.9.2

12.3. 断言

在日常英语中, assert 语句是说:“我断言这个条件为真, 如果不为真,程序中什么地方就有一个缺陷。”

不同与语法检查。 “断言”是针对逻辑或语义的检查,确保代码不会做错的事情。 这些逻辑检查由 assert 语句执行。如果检查失败,就会抛出异常。 在代码中, assert语句包含以下部分:

  • assert 关键字;

  • 条件(即求值为 TrueFalse 的表达式);

  • 逗号;

  • 当条件为 False 时显示的字符串。

12.3.1. 在Python中使用断言

例如,在交互式环境中输入以下代码:

>>> podBayDoorStatus = 'open'
>>> assert podBayDoorStatus == 'open', 'The pod bay doors need to be "open".'

这里将 podBayDoorStatus 设置为 open ,所以从此以后, 我们充分期望这个变量的值是 open 。在使用这个变量的程序中, 基于这个值是 open 的假定,我们可能写下了大量的代码, 即这些代码依赖于它是 open ,才能按照期望工作。 所以添加了一个断言,确保假定 podBayDoorStatusopen 是对的。 这里,我们加入了信息 'The pod bay doors need to be "open".', 这样如果断言失败,就很容易看到哪里出了错。

然后,假如我们犯了一个明显的错误, 把另外的值赋给 podBayDoorStatus 。 这个断言会抓住这个错误,清楚地告诉我们出了什么错。

>>> podBayDoorStatus = 'I\'m sorry, Dave. I\'m afraid I can\'t do that.'
>>> assert podBayDoorStatus == 'open', 'The pod bay doors need to be "open".'
---------------------------------------------------------------------------

AssertionError                            Traceback (most recent call last)

<ipython-input-17-d0e58c956fd5> in <module>
      1 podBayDoorStatus = 'I\'m sorry, Dave. I\'m afraid I can\'t do that.'
----> 2 assert podBayDoorStatus == 'open', 'The pod bay doors need to be "open".'


AssertionError: The pod bay doors need to be "open".

12.3.2. 在交通灯模拟中使用断言

假定你在编写一个交通信号灯的模拟程序。代表路口信号灯的数据结构是一个 字典,以 'ns' 和’ 'ew' 为键,分别表示南北向和东西向的信号灯。这些键的值可以 是'green''yellow''red'之一。代码看起来可能像这样:

>>> market_2nd = {'ns': 'green', 'ew': 'red'}
>>> mission_16th = {'ns': 'red', 'ew': 'green'}

这两个变量将针对 Market 街和第2街路口, 以及 Mission 街和第16街路口。作为项目启动, 你希望编写一个 switchLights() 函数, 它接受一个路口字典作为参数,并切换红绿灯。

开始你可能认为, switchLights() 只要将每一种 灯按顺序切换到下—种顔色:'green' 值应该切换 到 'yellow' , 'yellow' 应该切换到 'red''red'应该切换到'green'。实现这个思想的 代码看起来像这样:

>>> def swithLights(stoplight):
>>>     for key in stoplight.keys():
>>>         if stoplight[key] == 'green':
>>>             stoplight[key] = 'yellow'
>>>         elif stoplight[key] == 'yellow':
>>>             stoplight[key] = 'red'
>>>         elif stoplight[key] == 'red':
>>>             stoplight[key] = 'green'
>>>     # assert 'red' in stoplight.values(), 'Neither light is red! ' + str(stoplight)
>>> swithLights(market_2nd)

你可能已经发现了这段代码的问题,但假设你编写了剩下的模拟代码, 有几千行,但没有注意到这个问题。当最后运行时, 程序没有崩溃,但虚拟的汽车撞车了!因为你已经编写了剩下的程序, 所以不知道缺陷在哪里。也许在模拟汽车的代 码中,或者在模拟司机的代码中。可能需要花几个小时追踪缺陷, 才能找到 switchLights() 函数。

但如果在编写 switchLights() 时,你添加了断言, 确保至少一个交通灯是红色,可能在函数的底部添加这样的代码:

assert 'red' in stoplight.values(), 'Neither light is red! ' + str(stoplight)
>>> def swithLights(stoplight):
>>>     for key in stoplight.keys():
>>>         if stoplight[key] == 'green':
>>>             stoplight[key] = 'yellow'
>>>         elif stoplight[key] == 'yellow':
>>>             stoplight[key] = 'red'
>>>         elif stoplight[key] == 'red':
>>>             stoplight[key] = 'green'
>>>     assert 'red' in stoplight.values(), 'Neither light is red! ' + str(stoplight)

下面的语句多运行几遍,然后才会发现问题:

>>> for ii in range(100):
>>>     print(ii)
>>>     swithLights(market_2nd)
0
1
2
---------------------------------------------------------------------------

AssertionError                            Traceback (most recent call last)

<ipython-input-21-7486faab48f0> in <module>
      1 for ii in range(100):
      2     print(ii)
----> 3     swithLights(market_2nd)


<ipython-input-20-3bd00e37fe0e> in swithLights(stoplight)
      7         elif stoplight[key] == 'red':
      8             stoplight[key] = 'green'
----> 9     assert 'red' in stoplight.values(), 'Neither light is red! ' + str(stoplight)


AssertionError: Neither light is red! {'ns': 'yellow', 'ew': 'green'}

在实际情况中,很可能多次运行也没有问题,但程序会在某次运行时出错。 有了断言,程序就会崩溃,并提供出错。

这里重要的一行是 AssertionError() 。 虽然程序崩溃并非如你所愿,但它马上指出了逻辑失败:两个方向都没有红灯, 这意味着两个方向的车都可以走。 在程序执行中尽早快速失败,可以省去将来大量的调试工作。

12.3.3. 与“异常”用法的区别

不像异常,代码不应该用 tryexcept 处理 assert 语句。 如果 assert 失败,程序就应该崩溃。 通过这样的快速失败,产生缺陷和你第一次注意到该缺陷之间的时间就缩短了。 这将减少为了寻找导致该缺陷的代码,而需要检查的代码量。

断言针对的是程序员的错误,而不是用户的错误。 对于那些可以恢复的错误(诸如文件没有找到,或用户输入了无效的数据), 请抛出异常,而不是用 assert 语句检测它。

12.3.4. 禁用断言

在运行 Python 时传入 -O 选项,可以禁用断言。 如果你已完成了程序的编写和测试,不希望执行逻辑检测, 从而减慢程序的速度,这样就很好(尽管大多数断言语句所花的时间,不会让你觉察到速度的差异)。

断言是针对开发的,不是针对最终产品。当你将程序交给其他人运行时,它应该没有缺陷,不需要进行逻辑检查。