>>> 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 关键字;

  • 条件(即求值为 True 或 False 的表达式);

  • 逗号;

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

12.3.1. 在Python中使用断言

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

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

这里将 podBayDoorStatus 设置为 open ,所以从此以后, 我们充分期望这个变量的值是 open 。在使用这个变量的程序中, 基于这个值是 open 的假定,我们可能写下了大量的代码, 即这些代码依赖于它是 open ,才能按照期望工作。 所以添加了一个断言,确保假定 podBayDoorStatus 是 open 是对的。 这里,我们加入了信息 '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. 与“异常”用法的区别

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

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

12.3.4. 禁用断言

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

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