Python核心技术与实战——二十|assert的合理利用

我们平时在看代码的时候,或多或少会看到过assert的存在,并且在有些code review也可以通过增加assert来使代码更加健壮。但是即便如此,assert还是很容易被人忽略,可是这个很不起眼的用法,如果用的得当的话,会对我们的代码大有裨益。所以,我们今天就来看一看assert的用法。

什么是assert?

Python的assert可以被看做是一个debug的工具,主要测试一个条件是否满足,如果测试的条件满足,则什么也不执行,相当执行了pass语句;而如果条件不符合,则会抛出AssertionError,并返回具体的错误信息(optional)。他的具体语法是这样的

assert_stmt ::='assert' expression [',',Exception]

我们看看一个简单形式的assert expression的例子:

assert 1 == 2

就相当于下面的两行代码:

if __debug__:
    if not expression  : raise AssertionError

再开看看另外一种格式

assert 1 == 2,'assertion is wrong'

就相当于下面的两行代码格式

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

这里的__debug__是一个常数,如果Python程序执行时候带了-o这个选项,比如

Python -O test.py 

那么,程序中所有assert语句都会失效,常数__debug__就为False,反之则为True。

不过要注意的是,直接对常数赋值是非法的,因为他的值在解释器运行的时候已经决定了,中途是无法改变的。

另外还要记住一点:在使用assert的时候一定不要加括号,比如下面的形式

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

因为这样写的话,无论表达式是否正确,assert检查都不会是fail,程序只会给出SyntaxWarning的消息,正确的写法是这样的

assert 1 ==2 ,'this should fail'
##########输出##########
AssertionError: this should fail

总的来说,asssert在程序中的作用,是对代码做一些internal的self-check。使用assert,就表示很确定这个条件一定会发生或者一定不会发生。

举个例子,比如这里有一个函数需要给定的参数是人的性别,而正常情况性别之分男和女,我们就可以使用assert来防止程序的非法输入。如果程序没有bug那么assert永远不会抛出异常,而一旦抛出异常就很容易定位程序是在哪里出了问题,便于定位。

assert的用法

讲完了assert的基本语法和概念,我们下面通过一些实际的场景来看看assert在Python中的用法,并弄清楚assert的使用场景。

第一个例子。我们假设超时在做促销活动,准备对一些商品进行打折,那么后台就需要做一个apply_discount()的函数,要求输入为原来的价格和折扣,输出是折扣以后的价格,那么,我们就可以大概写成下面的样子:

def apply_discount(price,discount):
    update_price = price*(1-discount)
    assert 0 < update_price<price,'price should be greated or equal to 0'
    return update_price

然后我们可以通过给定几组数来验证一下他的功能

print(apply_discount(100,0.8))
##########输出##########
19.999999999999996


print(apply_discount(100,2))
##########输出##########
AssertionError: price should be greated or equal to 0

可以看出来,如果discount是0.2的时候,输出是正常的,但是discount如果成了2时,程序就会抛出异常了。这个时候,开发人员修改相关代码或增加进新功能的时候,在运行测试的时候非常容易看出问题。所以,assert的加入,可以有效的预防bug的发生,提高程序的健壮性。

第二个例子,最常见的除法操作是在哪个领域都会遇到的,比方我们想知道一批货物的销售的平均价格,就要给定销售总额还有销售数,这样平均售价就能算出来。

def calculate_average_price(totle_sales,num_sales):
    assert num_sales > 0,'number of sales should be greater than 0'
    return totle_sales/num_sales

同样我们在函数里增加了assert语句,规定了销售的总数量必须大于0,这样就可以防止后台计算错误。

除了上面两个例子,在实际工作中,assert还有一些很常见的用法,比方下面的场景:

def fun(input):
    assert isinstance(input,list),'input must be type of list'
    if len(input) == 1:
        pass
    elif len(input) == 2:
        pass
    else:
        pass

函数里的操作是基于input是个list这个前提,我们函数开始的地方加一个assert检查,防止程序出错。

但是要注意的是,在这个函数中加了assert的前提是我们十分确定程序的输入是一个list,而不能是其他的数据类型。如果我们的这个函数是个多态的,针对不同的数据类型有不同的操作,那就应该写成if...else...的条件语句了

def fun(input):
    if isinstance(input,int):
        pass
    elif isinstance(input,str):
        pass
    else:
        pass

assert的错误示例

通过前面讲的assert的使用场景,有可能会让我们比较迷茫——很多地方都可以使用assert,那么很多if...else的条件语句是不是也可以换成assert呢?这种想法可能就不准确了。接下来,我们看一看几个典型的错误用法:

比方我们要删除一些数据,但是删除操作必须是admin用户才可以,那么就有了下面的代码,

def delete_data(user,data_id):
    assert user_is_admin(user),'user must  admin'
    assert data_exist(data_id),'data must  exist'
    delete(data_id)

那么上面的代码有什么问题么?

assert的检查是可以被关闭的,在运行Python程序时候,加入一个-O选项就会使assert失效。因此,一旦assert的检查被关闭,user_is_admin()和course_exist()这两个函数就不会执行,就会导致下面的问题:

1.任何用户都可以删除数据;

2.不管数据是否存在,都可以墙纸执行删除操作

这就会给程序带来巨大的安全漏洞,正确的做法,是使用条件语句进行相应的检查,然后抛出相关的异常信息。

def delete_data(user,data_id):
    if not user_is_admin(user):
        raise Exception('user must be admin')
    if not data_exist(data_id):
        raise Exception('data must exist')
    delete(data_id)

再来看一个例子,我们想打开一个文件,进行数据的处理,读取等一系列操作,那么下面的写法也是有风险的

def read_and_process(path):
    assert file_exist(path),'file must exist'
    with opne(path) as f:
        pass

因为assert的使用,表明强行指定文件必须存在,但事实情况下,这个假设是不成立的,另外,打开文件操作也可能触发别的异常。所以正确的做法是用try...except来解决

def read_and_process(path):
    try:
        with open(path) as f:
            pass
    except Exception as e:
        pass

总得来说,assert是不实用于run-time error的检查,比方试图打开一个文件,但文件不存在;或者想要从网上down一个文件,但是中途断网了等。这些情况下我们一般使用错误和异常处理

总结

我们今天学习了assert的用法——assert通常用来对代码进行必要的self-check,表明我们在写代码的时候很确定这种情况一定会发生,或者一定不会发生。需要注意的是,使用assert的时候,一定不能加括号,否则无论表达式对与错,assert的检查永远不会fail。另外,程序中的assert,可以通过-O等选项被全局disable。

通过几个场景的应用,我们可以发现assert的合理使用可以增加代码的健壮度,同时也方便了程序出错时开发人员的定位排查。

不过,我们也要注意assert的使用场合,大多数情况下程序中出现的不同情况都是意料之中的,西药我们用不同的方案来处理,这时候条件语句就更加合适,而程序中的一些run-time error,异常处理就更加合适

posted @ 2019-12-04 13:30  银色的音色  阅读(501)  评论(0编辑  收藏  举报