首页 > 编程语言 >Python 用断言的使用

Python 用断言的使用

时间:2024-05-13 16:21:51浏览次数:24  
标签:语句 product 断言 Python assert 使用 bug

Python 用断言的使用

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_discount
     assert 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,它不是用于处理运行时错误的机制。
  • 设置解释器可全局禁用断言。

标签:语句,product,断言,Python,assert,使用,bug
From: https://www.cnblogs.com/wenyuan519/p/18189447

相关文章

  • Python 逗号的巧用
    Python逗号的巧用如果需要在中的列表、字典或集合常量中添加或移除项,记住一个窍门:在所有行后面都添加一个逗号。还不太明白?来看一个示例。假设在代码中有下面这个由名字组成的列表:>>>names=['Alice','Bob','Dilbert']在修改这个名字列表时,通过gitdiff查看改动可能有点......
  • Python调用终端模拟红绿灯
    Python调用终端模拟红绿灯一、需求分析1.需要实现的功能(1)通过控制台输入绿灯、黄灯、红灯的时间(2)输入完成后,按回车,先绿灯倒计时,然后黄灯倒计时,然后红灯倒计时,再到绿灯倒计时,周而复始。2.对类的分析静态特征(1)三个数字:红灯、黄灯、绿灯(2)两个电子屏一个电子屏显示一个数......
  • Python操作数据库
    简介pymysql:纯Python实现的一个驱动。因为是纯Python编写的,因此执行效率不如MySQL-python。并且也因为是纯Python编写的,因此可以和Python代码无缝衔接。MySQLConnector/Python:MySQL官方推出的使用纯Python连接MySQL的驱动。因为是纯Python开发的,效率不高。MySQL-python:......
  • 使用 Docker 部署 WebTop 运行 Linux 系统
    1)项目介绍GitHub:https://github.com/linuxserver/docker-webtopWebTop它是一个基于Linux(Ubuntu和Alpine两种版本)的轻量级容器,具有在浏览器中运行的完整桌面环境,具有基本的窗口管理器、像素完美的渲染分辨率、音频支持、剪贴板支持、屏幕键盘支持,以及用于上传/下载的远程......
  • react native 项目使用 Xcode 打包上架 App Store
    一、创建证书、标识符和描述文件等:1.前提条件可正常运行和打包的代码、Apple开发者账号点击注册Apple开发者账号注册完进入页面可以看到证书、标识符和描述文件创建入口2.创建AppID点击Identifiers旁边的加号选择AppIDs,点击Continue。选择App,点击Conti......
  • Java使用Socket传输数据
    importsun.swing.UIAction;importjavax.swing.*;importjava.awt.*;importjava.awt.event.ActionEvent;importjava.awt.event.ActionListener;importjava.io.*;importjava.net.InetSocketAddress;importjava.net.ServerSocket;importjava.net.Socket;import......
  • 使用 Python 旋转PDF页面、或调整PDF页面顺序
    在将纸质文档扫描成PDF电子文档时,有时可能会出现页面方向翻转或者页面顺序混乱的情况。为了确保更好地浏览和查看PDF文件,本文将分享一个使用Python来旋转PDF页面或者调整PDF页面顺序的解决方案。要实现Python对PDF页面进行设置,我们需要用到第三方库 Spire.PDFforPython。该库......
  • div,span使用展示\n字符串是换行而不是空格
    当前接口返回值有时候包含‘\n’时,我们想让它展示成换行,但是经常展示成空格 处理方法:1,使用split(‘\n’)分割当前字符串,然后使用数组渲染,(麻烦)2,使用css属性 (方便)文档white-space:pre-line ......
  • 使用python在windows系统操作快捷方式
    其实问题是由上一篇文章(https://www.cnblogs.com/anpengapple/p/18179353)的结尾引出来的。不需要了解背景的话,我现在需要做的是,右键打开桌面上的chrome快捷方式的属性,在目标的后面增加一个参数。我不想傻傻地手动添加,想交给程序来处理。 首先需要简单来说一下,windows的快捷方式......
  • 如何优雅的使用ollama| 京东云技术团队
     入门开源大语言模型,最好的工具就是ollama,这是一款简单的大模型本地部署框架,支持基于命令行的方式运行多种大语言模型,并提供了相应的Python和JSSDK,可以基于此方便实现ChatbotUI。这篇文章就以京东云智算平台为例(其他平台也是类似,甚至可以在本地电脑运行),分享如何一键安装olla......