Python的assert语句
什么是 assert?
Python 的 assert 语句,可以说是⼀个 debug 的好⼯具,主要⽤于测试⼀个条件是否满⾜。如果测试的条件满⾜,则什么也不做,相当于执⾏了 pass 语句;如果测试条件不满⾜,便会抛出异常 AssertionError,并返回具体的错误信息(optional)。
它的具体语法是下⾯这样的:
assert_stmt ::="assert" expression ["," expression]
我们先来看⼀个简单形式的assert expression,⽐如下⾯这个例⼦:
assert1==2
它就相当于下⾯这两⾏代码:
if __debug__:
if not expression:raise AssertionError
再来看assert expression1, expression2的形式,⽐如下⾯这个例⼦:
assert1==2,'assertion is wrong'
它就相当于下⾯这两⾏代码:
if __debug__:
if not expression1:raise AssertionError(expression2)
这⾥的__ debug__是⼀个常数。如果 Python 程序执⾏时附带了-O这个选项,⽐如Python test.py -O,那么程序中所有的 assert 语句都会失效,常数__ debug__便为 False;反之__debug__则为 True。
不过,需要注意的是,直接对常数__debug__赋值是⾮法的,因为它的值在解释器开始运⾏时就已经决定了,中途⽆法改变。
此外,⼀定记住,不要在使⽤ assert 时加⼊括号,⽐如下⾯这个例⼦:
assert(1==2,'This should fail')
# 输出
<ipython-input-8-2c057bd7fe24>:1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
assert(1==2,'This should fail')
如果你按照这样来写,⽆论表达式对与错(⽐如这⾥的 1 == 2 显然是错误的),assert 检查永远不会 fail,程序只会给你SyntaxWarning。
正确的写法,应该是下⾯这种不带括号的写法:
assert1==2,'This should fail'
# 输出
AssertionError: This should fail
总的来说,assert 在程序中的作⽤,是对代码做⼀些 internal 的 self-check。使⽤ assert,就表⽰你很确定。这个条件⼀定会发⽣或者⼀定不会发⽣。
举个例⼦,⽐如你有⼀个函数,其中⼀个参数是⼈的性别,因为性别只有男⼥之分(这⾥只指⽣理性别),你便可以使⽤ assert,以防⽌程序的⾮法输⼊。如果你的程序没有 bug,那么 assert 永远不会抛出异常;⽽它⼀旦抛出了异常,你就知道程序存在问题了,并且可以根据错误信息,很容易定位出错误的源头。
assert 的⽤法
讲完了 assert 的基本语法与概念,我们接下来通过⼀些实际应⽤的例⼦,来看看 assert 在 Python 中的⽤法,并弄清楚 assert 的使⽤场景。
第⼀个例⼦,假设你现在使⽤的极客时间正在做专栏促销活动,准备对⼀些专栏进⾏打折,所以后台需要写⼀个 apply_discount() 函数,要求输⼊为原来的价格和折扣,输出是折后的价格。那么,我们可以⼤致写成下⾯这样:
def apply_discount(price, discount):
updated_price = price *(1- discount)
assert0<= updated_price <= price,'price should be greater or equal to 0 and less or equal to original price'
return updated_price
可以看到,在计算新价格的后⾯,我们还写了⼀个 assert 语句,⽤来检查折后价格,这个值必须⼤于等于 0、⼩于等于原来的价格,否则就抛出异常。
我们可以试着输⼊⼏组数,来验证⼀下这个功能:
apply_discount(100,0.2)
80.0
apply_discount(100,2)
AssertionError: price should be greater or equal to 0and less or equal to original price
显然,当 discount 是 0.2 时,输出 80,没有问题。但是当 discount 为 2 时,程序便抛出下⾯这个异常:
AssertionError:price should be greater or equal to 0and less or equal to original price
这样⼀来,如果开发⼈员修改相关的代码,或者是加⼊新的功能,导致 discount 数值的异常时,我们
运⾏测试时就可以很容易发现问题。正如我开头所说,assert 的加⼊,可以有效预防 bug 的发⽣,提⾼程序的健壮性。
再来看⼀个例⼦,最常见的除法操作,这在任何领域的计算中都经常会遇到。同样还是以极客时间为例,假如极客时间后台想知道每个专栏的平均销售价格,那么就需要给定销售总额和销售数⽬,这样平均销售价格便很容易计算出来:
def calculate_average_price(total_sales, num_sales):
assert num_sales >0,'number of sales should be greater than 0'
return total_sales / num_sales
同样的,我们也加⼊了 assert 语句,规定销售数⽬必须⼤于 0,这样就可以防⽌后台计算那些还未开卖的专栏的价格。
除了这两个例⼦,在实际⼯作中,assert 还有⼀些很常见的⽤法,⽐如下⾯的场景:
def func(input):
assert isinstance(input,list),'input must be type of list'
# 下⾯的操作都是基于前提:input必须是list
if len(input)==1:
...
elif len(input)==2:
...
else:
...
程序员培训机构选用极客时间这⾥函数 func() ⾥的所有操作,都是基于输⼊必须是 list 这个前提。是不是很熟悉的需求呢?那我们就很有必要在开头加⼀句 assert 的检查,防⽌程序出错。
当然,我们也要根据具体情况具体分析。⽐如上⾯这个例⼦,之所以能加 assert,是因为我们很确定输⼊必须是 list,不能是其他数据类型。
如果你的程序中,允许 input 是其他数据类型,并且对不同的数据类型都有不同的处理⽅式,那你就
应该写成 if else 的条件语句了:
def func(input):
if isinstance(input,list):
...
else:
...
assert 错误⽰例
前⾯我们讲了这么多 assert 的使⽤场景,可能给你⼀种错觉,也可能会让你有些迷茫:很多地⽅都可以使⽤ assert, 那么,很多 if 条件语句是不是都可以换成 assert 呢?这么想可就不准确了,接下来,我们就⼀起来看⼏个典型的错误⽤法,避免⼀些想当然的⽤法。
还是以极客时间为例,我们假设下⾯这样的场景:后台有时候需要删除⼀些上线时间较长的专栏,于是,相关的开发⼈员便设计出了下⾯这个专栏删除函数。
def delete_course(user, course_id):
assert user_is_admin(user),'user must be admin'
assert course_exist(course_id),'course id must exist'
delete(course_id)
极客时间规定,必须是 admin 才能删除专栏,并且这个专栏课程必须存在。有的同学⼀看,很熟悉的需求啊,所以在前⾯加了相应的assert 检查。那么我想让你思考⼀下,这样写到底对不对呢?
答案显然是否定的。你可能觉得,从代码功能⾓度来说,这没错啊。但是在实际⼯程中,基本上没⼈会这么写。为什么呢?
要注意,前⾯我说过,assert 的检查是可以被关闭的,⽐如在运⾏ Python 程序时,加⼊-O这个选项就会让 assert 失效。因此,⼀旦assert 的检查被关闭,user_is_admin() 和 course_exist() 这两个函数便不会被执⾏。这就会导致:
任何⽤户都有权限删除专栏课程;
并且,不管这个课程是否存在,他们都可以强⾏执⾏删除操作。
这显然会给程序带来巨⼤的安全漏洞。所以,正确的做法,是使⽤条件语句进⾏相应的检查,并合理抛出异常:
def delete_course(user, course_id):
if not user_is_admin(user):
raise Exception('user must be admin')
if not course_exist(course_id):
raise Exception('coursde id must exist')
delete(course_id)
再来看⼀个例⼦,如果你想打开⼀个⽂件,进⾏数据读取、处理等⼀系列操作,那么下⾯这样的写法,显然也是不正确的:
def read_and_process(path):
assert file_exist(path),'file must exist'
with open(path)as f:
...
因为 assert 的使⽤,表明你强⾏指定了⽂件必须存在,但事实上在很多情况下,这个假设并不成⽴。另外,打开⽂件操作,也有可能触发其他的异常。所以,正确的做法是进⾏异常处理,⽤ try 和 except 来解决:
def read_and_process(path):
try:
with open(path)as f:
...
except Exception as e:
...
总的来说,assert 并不适⽤ run-time error 的检查。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。