一、使用python中的断言来自动检测python程序中的错误,让程序更加可靠且更易于调试
从根本上来说,python中的断言语句是一种调试工具,用来测试某个断言条件,如果断言条件为True,则程序将继续正常执行;但如果断言条件为假False,则会引发AssertionError异常并显示相关的错误消息。
示例:
假设你需要使用python构建在线商城,为了添加打折优惠功能,你编写了下面这个apply_discount函数:
def apply_discount(product, discount):
price = int(product['price'] * (1 - discount))
assert 0 <= price <= product['price'] # 断言
return price
注意到assert语句了吗?这条语句确保在任何情况下,通过该函数计算的折后价格不低于0,也不会高于产品原价。
下面创建一个示例产品,一双价格为1000元的鞋子,现在如果为这双鞋打五折,则价格应该变为500,对该函数进行测试:
def apply_discount(product, discount):
price = int(product['price'] * (1 - discount))
assert 0 <= price <= product['price']
return price
if __name__ == "__main__":
shoes = {
"name": "鞋子",
"price": 1000,
}
price = apply_discount(shoes, 0.5)
print(price)
还不错,接着我们再尝试使用一些无效的折扣,比如200%的”折扣“会让商家向顾客付钱:
从上面的信息可以看到,当尝试使用无效的折扣时,程序会停止并触发一个AssertionError。发生这种情况是因为200%的折扣违反了在apply_discount函数中设置的断言条件,从异常栈的跟踪信息中还能得知断言验证失败的具体位置。
二、为什么不用普通的异常来处理?
断言是为了告诉开发人员程序中发生了不可恢复的错误,对于可以预料的错误(如未找到相关文件),用户可以予以纠正或重试,断言并不是为此而生的。
如果程序没有bug,那么这些断言条件永远也不会触发,但如果违反了断言条件,程序就会崩溃并报告断言错误,告诉开发人员究竟违反了哪个“不可能”的情况,这样可以更轻松的追踪和修复程序中的bug。python中的断言语句是一种调试辅助功能,不是用来处理运行时错误的机制,使用断言的目的是让开发人员更快速的找到可能导致bug的根本原因,除非程序中存在bug,否则绝不会抛出断言错误。
三、assert语法及使用注意事项
语法:
assert <断言判断条件> [,"错误提示消息"]
其中的错误提示消息是可选的,如果加了错误提示消息,则会在抛出异常时,同异常消息一同展示,如:
注意事项:
在Python中使用断言时,需要注意,第一:断言会给应用程序带来安全风险和bug;第二:容易写出很多无用的断言语句。
1、不用使用断言验证数据
在python中使用断言时要注意的一个重点是,若在命令行中使用-o和-oo标识,或修改CPython中的PYTHONOPTIMIZE环境变量,都会全局禁用断言。此时所有的断言语句都会无效,程序会直接略过而不处理断言,因此不会执行任何条件表达式,因此使用断言语句来快速验证输入数据非常危险。
下面用一个简单的例子说明这个问题,前面一样,假设使用Python构建一个在线商城,代码中有一个函数会根据用户的请求来删除产品,于是写下这样的实现:
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()
仔细看这个函数,如果断言被禁用后会发生什么?这个仅有三行代码的函数示例存在两个严重的问题:
(1)使用断言语句检查管理员权限很危险,如果在Python解释器中禁用了断言,这行代码则会变为空操作,不会执行权限检查,之后任何用户都可以删除产品,这可能会引发安全问题,攻击者可能会借此摧毁或严重破会在线商城中的数据。
(2)禁用断言后会跳过has_product()检查,这意味着可以使用无效的产品ID调用get_product(),这可能会导致更严重的bug,具体情况取决于程序的编写方式,在最糟糕的情况下,有人可以借此对商城发起拒绝服务攻击。例如:如果尝试删除未知产品会导致商城应用程序崩溃,那么 攻击者就可以发送大量无效的删除请求让程序无法正常工作。
那么如何避免这些问题呢?答案是绝对不要使用断言来验证数据,而是使用常规的 if 语句验证,并在必要的时候触发验证异常,如下所示:
def delete_product(prod_id, user):
if not user.is_admin():
raise AuthError("Must be admin.")
if not store.has_product(prod_id):
raise ValueError("Unknown product.")
store.get_product(prod_id).delete()
修改后的示例还有个好处,即代码不会触发通用的AssertionError异常,而是触发与语义相关的异常,如ValueError或AuthError。
2、永不失败的断言
开发人员很容易就会添加许多总是为True的python断言,因此这些语句毫无用处,永远不会触发异常。
四、总结
尽管有这些需要注意的事项,但Python的断言依然是功能强大的调试工具,了解断言的工作方式及其使用场景有助于编写更易于维护和调试的Python程序,学习断言有助于将个人的python知识提升到新的水平。
关键要点:python断言语句是一种测试某个条件的调试辅助功能,可作为程序的内部自检
断言应该只用于帮助开发人员识别bug,它不是用于处理运行时异常的机制
设置解释器可全局禁用断言