分析在Python中何种情况下需要使用断言


Posted in Python onApril 01, 2015

这个问题是如何在一些场景下使用断言表达式,通常会有人误用它,所以我决定写一篇文章来说明何时使用断言,什么时候不用。

为那些还不清楚它的人,Python的assert是用来检查一个条件,如果它为真,就不做任何事。如果它为假,则会抛出AssertError并且包含错误信息。例如:
 

py> x = 23
py> assert x > 0, "x is not zero or negative"
py> assert x%2 == 0, "x is not an even number"
Traceback (most recent call last):
File "", line 1, in
AssertionError: x is not an even number

很多人用assert作为一个很快和容易的方法来在参数错误的时候抛出异常。但这样做是错的,非常错误,有两个原因。首先AssertError不是在测试参数时应该抛出的错误。你不应该像这样写代码:
 

if not isinstance(x, int):
raise AssertionError("not an int")

你应该抛出TypeError的错误,assert会抛出错误的异常。

但是,更危险的是,有一个关于assert的困扰:它可以被编译好然后从来不执行,如果你用 ?O 或 ?oo 选项运行Python,结果不保证assert表达式会运行到。当适当的使用assert时,这是未来,但是当assert不恰当的使用时,它会让代码用-O执行时出错。

那什么时候应该使用assert?没有特定的规则,断言应该用于:

  •     防御型的编程
  •     运行时检查程序逻辑
  •     检查约定
  •     程序常量
  •     检查文档

(在测试代码的时候使用断言也是可接受的,是一种很方便的单元测试方法,你接受这些测试在用-O标志运行时不会做任何事。我有时在代码里使用assert False来标记没有写完的代码分支,我希望这些代码运行失败。尽管抛出NotImplementedError可能会更好。)

关于断言的意见有很多,因为它能确保代码的正确性。如果你确定代码是正确的,那么就没有用断言的必要了,因为他们从来不会运行失败,你可以直接移除这些断言。如果你确定检查会失败,那么如果你不用断言,代码就会通过编译并忽略你的检查。

在以上两种情况下会很有意思,当你比较肯定代码但是不是绝对肯定时。可能你会错过一些非常古怪的情况。在这个情况下,额外的运行时检查能帮你确保任何错误都会尽早地被捕捉到。

另一个好的使用断言的方式是检查程序的不变量。一个不变量是一些你需要依赖它为真的情况,除非一个bug导致它为假。如果有bug,最好能够尽早发现,所以我们为它进行一个测试,但是又不想减慢代码运行速度。所以就用断言,因为它能在开发时打开,在产品阶段关闭。

一个非变量的例子可能是,如果你的函数希望在它开始时有数据库的连接,并且承诺在它返回的时候仍然保持连接,这就是函数的不变量:
 

def some_function(arg):
  assert not DB.closed()
  ... # code goes here
  assert not DB.closed()
  return result

断言本身就是很好的注释,胜过你直接写注释:

# when we reach here, we know that n > 2

你可以通过添加断言来确保它:

assert n > 2

断言也是一种防御型编程。你不是让你的代码防御现在的错误,而是防止在代码修改后引发的错误。理想情况下,单元测试可以完成这样的工作,可是需要面对的现实是,它们通常是没有完成的。人们可能在提交代码前会忘了运行测试代码。有一个内部检查是另一个阻挡错误的防线,尤其是那些不明显的错误,却导致了代码出问题并且返回错误的结果。

加入你有一些if…elif 的语句块,你知道在这之前一些需要有一些值:
 

# target is expected to be one of x, y, or z, and nothing else.
if target == x:
  run_x_code()
elif target == y:
  run_y_code()
else:
  run_z_code()

假设代码现在是完全正确的。但它会一直是正确的吗?依赖的修改,代码的修改。如果依赖修改成 target = w 会发生什么,会关系到run_w_code函数吗?如果我们改变了代码,但没有修改这里的代码,可能会导致错误的调用 run_z_code 函数并引发错误。用防御型的方法来写代码会很好,它能让代码运行正确,或者立马执行错误,即使你在未来对它进行了修改。

在代码开头的注释很好的一步,但是人们经常懒得读或者更新注释。一旦发生这种情况,注释会变得没用。但有了断言,我可以同时对代码块的假设书写文档,并且在它们违反的时候触发一个干净的错误
 

assert target in (x, y, z)
if target == x:
  run_x_code()
elif target == y:
  run_y_code()
else:
  assert target == z
  run_z_code()

这样,断言是一种防御型编程,同时也是一种文档。我想到一个更好的方案:
 

if target == x:
  run_x_code()
elif target == y:
  run_y_code()
elif target == z:
  run_z_code()
else:
  # This can never happen. But just in case it does...
  raise RuntimeError("an unexpected error occurred")

按约定进行设计是断言的另一个好的用途。我们想象函数与调用者之间有个约定,比如下面的:

“如果你传给我一个非空字符串,我保证传会字符串的第一个字母并将其大写。”

如果约定被函数或调用这破坏,代码就会出问题。我们说函数有一些前置条件和后置条件,所以函数就会这么写:
 

def first_upper(astring):
  assert isinstance(astring, str) and len(astring) > 0
  result = astring[0].upper()
  assert isinstance(result, str) and len(result) == 1
  assert result == result.upper()
  return result

按约定设计的目标是为了正确的编程,前置条件和后置条件是需要保持的。这是断言的典型应用场景,因为一旦我们发布了没有问题的代码到产品中,程序会是正确的,并且我们能安全的移除检查。

下面是我建议的不要用断言的场景:

  •     不要用它测试用户提供的数据
  •     不要用断言来检查你觉得在你的程序的常规使用时会出错的地方。断言是用来检查非常罕见的问题。你的用户不应该看到任何断言错误,如果他们看到了,这是一个bug,修复它。
  •     有的情况下,不用断言是因为它比精确的检查要短,它不应该是懒码农的偷懒方式。
  •     不要用它来检查对公共库的输入参数,因为它不能控制调用者,所以不能保证调用者会不会打破双方的约定。
  •     不要为你觉得可以恢复的错误用断言。换句话说,不用改在产品代码里捕捉到断言错误。
  •     不要用太多断言以至于让代码很晦涩。
Python 相关文章推荐
Hadoop中的Python框架的使用指南
Apr 22 Python
web.py 十分钟创建简易博客实现代码
Apr 22 Python
一个基于flask的web应用诞生 用户注册功能开发(5)
Apr 11 Python
Python有序字典简单实现方法示例
Sep 28 Python
django实现同一个ip十分钟内只能注册一次的实例
Nov 03 Python
python面试题小结附答案实例代码
Apr 11 Python
Python分析彩票记录并预测中奖号码过程详解
Jul 09 Python
python使用tomorrow实现多线程的例子
Jul 20 Python
浅析python 中大括号中括号小括号的区分
Jul 29 Python
基于python实现的百度新歌榜、热歌榜下载器(附代码)
Aug 05 Python
在python中使用pyspark读写Hive数据操作
Jun 06 Python
pycharm最新激活码有效期至2100年(亲测可用)
Feb 05 Python
用Python制作简单的朴素基数估计器的教程
Apr 01 #Python
简单的编程0基础下Python入门指引
Apr 01 #Python
python查找目录下指定扩展名的文件实例
Apr 01 #Python
Python利用多进程将大量数据放入有限内存的教程
Apr 01 #Python
python连接远程ftp服务器并列出目录下文件的方法
Apr 01 #Python
10种检测Python程序运行时间、CPU和内存占用的方法
Apr 01 #Python
深入Python解释器理解Python中的字节码
Apr 01 #Python
You might like
php绝对路径与相对路径之间关系的的分析
2010/03/03 PHP
destoon网站转移服务器后搜索汉字出现乱码的解决方法
2014/06/21 PHP
php自定义hash函数实例
2015/05/05 PHP
php实现阳历阴历互转的方法
2015/10/28 PHP
php+redis消息队列实现抢购功能
2018/02/08 PHP
firefox事件处理之自动查找event的函数(用于onclick=foo())
2010/08/05 Javascript
基于JQuery实现的类似购物商城的购物车
2011/12/06 Javascript
javascript实现图片切换的幻灯片效果源代码
2012/12/12 Javascript
在jQuery中 常用的选择器介绍
2013/04/16 Javascript
用jQuery向div中添加Html文本内容的简单实现
2016/07/13 Javascript
HTML Table 空白单元格补全的简单实现
2016/10/13 Javascript
vue2.0获取自定义属性的值
2017/03/28 Javascript
Vue2.0结合webuploader实现文件分片上传功能
2018/03/09 Javascript
node.js部署之启动后台运行forever的方法
2018/05/23 Javascript
浅谈Angular6的服务和依赖注入
2018/06/27 Javascript
ligerUI的ligerDialog关闭刷新的方法
2019/09/27 Javascript
Python实现PS滤镜的万花筒效果示例
2018/01/23 Python
tensorflow 中对数组元素的操作方法
2018/07/27 Python
Python脚本完成post接口测试的实例
2018/12/17 Python
解决在Python编辑器pycharm中程序run正常debug错误的问题
2019/01/17 Python
Python创建字典的八种方式
2019/02/27 Python
Python3 使用selenium插件爬取苏宁商家联系电话
2019/12/23 Python
python基于三阶贝塞尔曲线的数据平滑算法
2019/12/27 Python
python使用梯度下降算法实现一个多线性回归
2020/03/24 Python
浅谈PyTorch中in-place operation的含义
2020/06/27 Python
Python基于template实现字符串替换
2020/11/27 Python
全面介绍python中很常用的单元测试框架unitest
2020/12/14 Python
css3 clip实现圆环进度条的示例代码
2018/02/07 HTML / CSS
手工制作的意大利太阳镜和光学元件:Illesteva
2019/01/19 全球购物
意大利香水和化妆品购物网站:Parfimo.it
2019/10/06 全球购物
捷克家具销售网站:SCONTO Nábytek
2020/01/02 全球购物
说说你所熟悉或听说过的j2ee中的几种常用模式?及对设计模式的一些看法
2012/05/24 面试题
Ado与Ado.net的相同与不同
2014/12/08 面试题
个人批评与自我批评发言稿
2014/09/28 职场文书
2015年超市工作总结
2015/04/09 职场文书
Python中第三方库Faker的使用详解
2022/04/02 Python