zoukankan      html  css  js  c++  java
  • 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,异常处理就更加合适

  • 相关阅读:
    Javascript学习中比较核心的知识(持续更新)
    深入理解Builder模式(转载)
    Git 对文件进行批量rm操作
    Android 记录代码执行时间
    Git已跟踪文件的忽略方法
    Linux shell command line process(命令行处理流程)
    线程 方面笔记01
    c#中索引器
    sql记录
    颜色的处理
  • 原文地址:https://www.cnblogs.com/yinsedeyinse/p/11976266.html
Copyright © 2011-2022 走看看