Python 用断言的使用,有时,真正有用的语言特性得到的关注反而不多,比如内置的assert语句就没有受到重视。

本文将介绍如何在中使用断言。你将学习用断言来自动检测程序中的错误,让程序更可靠且更易于调试。

读到这里,你可能想知道什么是断言,以及它到底有什么好处。下面就来一一揭晓答案。

从根本上来说,的断言语句是一种调试工具,用来测试某个断言条件。如果断言条件为真,则程序将继续正常执行;但如果条件为假,则会引发AssertionError异常并显示相关的错误消息。

用断言的使用 示例

下面举一个断言能派上用场的简单例子。本书中的例子会尝试结合你可能在实际工作中遇到的问题。

假设你需要用构建在线商店。为了添加打折优惠券的功能,你编写了下面这个apply_discount函数:

def apply_discount(product, discount):price = int(product['price'] * (1.0 - discount))assert 0 <= price <= product['price']return price

注意到assert语句了吗?这条语句确保在任何情况下,通过该函数计算的折后价不低于0,也不会高于产品原价。
来看看调用该函数能否正确计算折后价。在这个例子中,商店中的产品用普通的字典表示。这样能够很好地演示断言的使用方法,当然实际的应用程序可能不会这么做。下面先创建一个示例产品,即一双价格为149美元的漂亮鞋子:

>>> shoes = {'name': 'Fancy Shoes', 'price': 14900}

顺便说一下,这里使用整数来表示以分为单位的价格,以此来避免货币的舍入问题。一般而言,这是个好办法……好吧,有点扯远了。现在如果为这双鞋打七五折,即优惠了25%,则售价变为111.75美元:

>>> apply_discount(shoes, 0.25)
11175

嗯,还不错。接着再尝试使用一些无效的折扣,比如200%的“折扣”会让商家向顾客付钱:

>>> apply_discount(shoes, 2.0)
Traceback (most recent call last):File "<input>", line 1, in <module>apply_discount(prod, 2.0)File "<input>", line 4, in apply_discountassert 0 <= price <= product['price']
AssertionError

从上面可以看到,当尝试使用无效的折扣时,程序会停止并触发一个AssertionError。发生这种情况是因为200%的折扣违反了在apply_discount函数中设置的断言条件。

从异常栈跟踪信息中还能得知断言验证失败的具体位置。如果你(或者团队中的另一个开发人员)在测试在线商店时遇到这些错误,那么查看异常回溯就可以轻松地了解是哪里出了问题。

这极大地加快了调试工作的速度,并且长远看来,程序也更易于维护。朋友们,这就是断言的力量。

用断言的使用 为什么不用普通的异常来处理

你可能很奇怪为什么不在前面的示例中使用if语句和异常。

要知道,断言是为了告诉开发人员程序中发生了不可恢复的错误。对于可以预料的错误(如未找到相关文件),用户可以予以纠正或重试,断言并不是为此而生的。

断言用于程序内部自检,如声明一些代码中不可能出现的条件。如果触发了某个条件,即意味着程序中存在相应的bug。

如果程序没有bug,那么这些断言条件永远不会触发。但如果违反了断言条件,程序就会崩溃并报告断言错误,告诉开发人员究竟违反了哪个“不可能”的情况。这样可以更轻松地追踪和修复程序中的bug。我喜欢能让生活变轻松的东西,你也是吧?

现在请记住,的断言语句是一种调试辅助功能,不是用来处理运行时错误的机制。使用断言的目的是让开发人员更快速地找到可能导致bug的根本原因。除非程序中存在bug,否则绝不应抛出断言错误。

下面先详细了解一下断言的语法,接着介绍在实际工作中使用断言时常见的两个陷阱。

用断言的使用 的断言语法

在开始使用的某项特性之前,最好先研究它是如何实现的。根据文档,assert语句的语法如下所示:详见文档:“The Assert Statement”。

assert_stmt ::= "assert" expression1 ["," expression2]

其中expression1是需要测试的条件,可选的expression2是错误消息,如果断言失败则显示该消息。在执行时,解释器将每条断言语句大致转换为以下这些语句:

if __debug__:if not expression1:raise AssertionError(expression2)

这段代码有两个有趣之处。

第一,代码在检查断言条件之前,还会检查__debug__全局变量。这是一个内置的布尔标记,在一般情况下为真,若进行代码优化则为假。下一节将进一步讨论。

第二,还可以使用expression2传递一个可选的错误消息,该消息将与回溯中的AssertionError一起显示,用来进一步简化调试。例如,我见过这样的代码:

>>> if cond == 'x':
...    do_x()
... elif cond == 'y':
...    do_y()
... else:
...    assert False, (
...        'This should never happen, but it does '
...        'occasionally. We are currently trying to '
...        'figure out why. Email dbader if you '
...        'encounter this in the wild. Thanks!')

虽然这段代码很丑,但如果在应用程序中遇到海森堡bug,那么这绝对是一种有效且有用的技术。

指在尝试研究时似乎会消失或者改变行为的bug,参见维基百科“海森堡bug”词条。

用断言的使用 常见陷阱

在中使用断言时,需要注意两点:第一,断言会给应用程序带来安全风险和bug;第二,容易形成语法怪癖,开发人员会很容易编写出许多无用的断言。

这些问题看上去(而且可能确实)相当严重,所以你应该至少对以下两个注意事项有所了解。

注意事项1:不要使用断言验证数据

在中使用断言时要注意的一个重点是,若在命令行中使用-O-OO标识,或修改C中的OPTIMIZE环境变量,都会全局禁用断言。

此时所有断言语句都无效,程序会直接略过而不处理断言,因此不会执行任何条件表达式。

许多其他的编程语言也有类似的设计决策。因此使用断言语句来快速验证输入数据非常危险。

进一步解释一下,如果程序使用断言来检查一个函数参数是否包含“错误”或意想不到的值,那么很快就会发现事与愿违并会导致错误或安全漏洞。

下面用一个简单的例子说明这个问题。与前面一样,假设你正在用构建一个在线商店应用程序,代码中有一个函数会根据用户的请求来删除产品。

由于刚刚学习了断言,因此你可能会急于在代码中使用(反正我会这么做)。于是,你写下这样的实现:

def delete_product(prod_id, user):assert user.is_admin(), 'Must be admin'assert store.has_product(prod_id), 'Unknown product'store.get_product(prod_id).delete()

仔细看这个delete_product函数,如果禁用断言会发生什么?

这个仅有三行代码的函数示例存在两个严重的问题,都是由不正确地使用断言语句引起的。

(1) 使用断言语句检查管理员权限很危险。如果在解释器中禁用断言,这行代码则会变为空操作,不会执行权限检查,之后任何用户都可以删除产品。这可能会引发安全问题,攻击者可能会借此摧毁或严重破坏在线商店中的数据。这太糟糕了!

(2) 禁用断言后会跳过has_product()检查。这意味着可以使用无效的产品ID调用get_product(),这可能会导致更严重的bug,具体情况取决于程序的编写方式。在最糟的情况下,有人可能借此对商店发起拒绝服务(denial of service,DoS)攻击。例如,如果尝试删除未知产品会导致商店应用程序崩溃,那么攻击者就可以发送大量无效的删除请求让程序无法工作。

那么如何避免这些问题呢?答案是绝对不要使用断言来验证数据,而是使用常规的if语句验证,并在必要时触发验证异常,如下所示:

def delete_product(product_id, user):if not user.is_admin():raise AuthError('Must be admin to delete')if not store.has_product(product_id):raise ValueError('Unknown product id')store.get_product(product_id).delete()

修改后的示例还有一个好处,即代码不会触发通用的AssertionError异常,而是触发与语义相关的异常,如ValueErrorAuthError(后者需要自行定义)。

注意事项2:永不失败的断言

开发人员很容易就会添加许多总是为真的断言,我过去一直犯这样的错误。长话短说,来看看问题所在。
在将一个元组作为assert语句中的第一个参数传递时,断言条件总为真,因此永远不会失败。
例如,这个断言永远不会失败:

assert(1 == 2, 'This should fail')

这是因为在中非空元组总为真值。如果将元组传递给assert语句,则会导致断言条件始终为真,因此上述assert语句毫无用处,永远不会触发异常。

这种不直观的行为很容易导致开发人员写出糟糕的多行断言。比如我曾经欢快地为一个测试套件写了一堆无用的测试用例,带来了并不真实的安全感。假设在单元测试中有这样的断言:

assert (counter == 10,'It should have counted all the items'
)

第一次检查时,这个测试用例看起来非常好。但它实际上永远不会得到错误的结果:无论计数器变量的状态如何,断言总是计算为True。为什么会这样?因为其中只是声明了一个布尔值总是为真的元组对象。

就像之前说的那样,这样很容易就会搬起石头砸自己的脚(我的脚仍然很痛)。有一个很好的对策能防止这种语法巧合导致的麻烦,那就是使用代码linter。新版本的 3也会对这些可疑断言给出语法警告。

顺便说一下,这也是为什么应该总是对单元测试用例先做一个快速的冒烟测试。要确保在编写下一个测试之前,当前测试用例的确会失败。

用断言的使用 总结

尽管有这些需要注意的事项,但的断言依然是功能强大的调试工具,且常常得不到充分的利用。

了解断言的工作方式及使用场景有助于编写更易维护和调试的程序。

学习断言有助于将你的知识提升到新的水平,让你成为一个全方位的高手。我确信这一点,因为断言让我在调试过程中节省了大量时间。

关键要点

  • 断言语句是一种测试某个条件的调试辅助功能,可作为程序的内部自检。
  • 断言应该只用于帮助开发人员识别bug,它不是用于处理运行时错误的机制。
  • 设置解释器可全局禁用断言。

Python 断言的使用相关推荐

  1. python断言assert实例_Python断言assert的用法代码解析

    在开发一个程序时候,与其让它运行时崩溃,不如在它出现错误条件时就崩溃(返回错误).这时候断言assert 就显得非常有用. python assert断言是声明布尔值必须为真的判定,如果发生异常就说明 ...

  2. Python 断言和异常

    Python 断言和异常 Python断言 断言是一种理智检查,当程序的测试完成,可以将其打开或关闭.断言的最简单方法就是把它比作raise-if语句(或更加准确,raise-if-not声明).一个 ...

  3. python断言语句_Python中的断言(Assertions in Python)

    Python中的断言(Assertions in Python) 断言是一种完整性检查,您可以在完成程序测试后打开或关闭. 想到断言的最简单方法是将它比作一个raise-if语句(或者更准确,即使是i ...

  4. python断言失败_python异常处理、自定义异常、断言原理与用法分析

    本文实例讲述了python异常处理.自定义异常.断言原理与用法.分享给大家供大家参考,具体如下: 什么是异常: 当程序遭遇某些非正常问题的时候就会抛出异常:比如int()只能处理能转化成int的对象, ...

  5. python断言assertequal_python-尝试断言AlmostEqual / assertEqual时,不受支持的操作数类型...

    我试图测试两个对象是否相等.该对象的类型是Point,它是由ROS(机器人操作系统)定义的类.我有以下测试: def test_when_getting_position_after_2s_then_ ...

  6. python断言失败_python异常处理、断言

    异常处理基本语法 捕获异常 try: 语句1 语句2 ... except ERRNAME as e: print(e) #尝试执行语句,捕获到ERRNAME异常时打印异常信息e 捕获多个异常 try ...

  7. python断言assert实例_Python 拓展之断言(assert)

    写在之前 大家好,这是首发在我公众号「Python空间」的第 60 篇文章,欢迎关注,期待和你的交流. 在昨天的文章中(零基础学习 Python 之处理异常)中学习了处理异常的正确姿势,今天我们继续来 ...

  8. 【python学习】python断言assert-30

    什么是断言呢? 断言就是世界结果与期望结果去对比,符合预期的测试就是pass,不符合预期的测试就是failed.python当中的 assert(断言)用于判断一个表达式,在表达式条件为 false ...

  9. python断言assertequal_python中那个断言assert的优化

    Python Assert 为何不尽如人意# Python中的断言用起来非常简单,你可以在assert后面跟上任意判断条件,如果断言失败则会抛出异常. Copy >>> assert ...

  10. python中断言语句的语法,python断言语句的语法【assert语句】

    assert语句又称作断言,指的是期望用户满足指定的条件.当用户定义的约束条件不满足的时候,它会触发AsserionError异常,所以assert语句可以当作条件式的raise语句.assert语句 ...

最新文章

  1. 如何用SPY++工具查看窗体的句柄
  2. 怎样使破解网页的禁止复制黏贴
  3. X86嵌入式主板在IOT网关产品的应用
  4. 百度地图API —— Hello World!
  5. kinux查日志_Linux实时查看日志的四种命令详解
  6. Shader Compiler 界面进展2
  7. JavaScript、HTML、CSS学习—思维导图
  8. 我的世界php开服环境_PHP初学者如何搭建环境,并在本地服务器(or云端服务器)运行自己的第一个PHP样例...
  9. mysql数据库中 pri_MySQL数据库管理——SQL指令集
  10. Shell脚本学习-阶段二十八-shell练习二
  11. HDU2073 无限的路【数学】
  12. Vue学习笔记(尚硅谷天禹老师)
  13. Spring全家桶+分布式微服务(十次方)
  14. ActiveX控件开发流程
  15. java命令行打包war_手工命令行打包java工程为war包
  16. gis生成道路中心线_ArcGIS方法-利用到路面提取道路中心线的方法
  17. NLPCC2013中文微博细粒度情感识别(二)
  18. 如何设置本电脑中的mysql让别人的电脑连接
  19. 【FPGA基础篇】底层结构组成
  20. 元器件学习——————三极管

热门文章

  1. 第七章 算术操作指令的实现
  2. 一个简单LEGv8处理器的Verilog实现【二】【指令相关基础知识与实验分析】
  3. linux清除ip地址命令,ip 命令 和ifconfig 命令 删除IP
  4. OneNote2013打开共享
  5. 电脑计算机快捷键切换桌面,电脑切换界面的快捷键是什么_电脑切换桌面快捷键怎么用-win7之家...
  6. 华硕顽石四代自主扩展内存条图解
  7. c语言200以内能被3和5整除的数,程式设计程式 求500以内能被3和5同时整除的所有数的和,c语言程式码写全一点,谢谢啦...
  8. 个人电脑bt文件服务器,简单实用 教你轻松架设个人BT服务器
  9. MongoDB学习笔记之索引(一)
  10. 二维高斯函数和正态分布