我们平时在看代码的时候,或多或少会看到过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,异常处理就更加合适

Python核心技术与实战——二十|assert的合理利用的更多相关文章

  1. Python核心技术与实战——二十|Python的垃圾回收机制

    今天要讲的是Python的垃圾回收机制 众所周知,我们现在的计算机都是图灵架构.图灵架构的本质,就是一条无限长的纸带,对应着我们的存储器.随着寄存器.异失性存储器(内存)和永久性存储器(硬盘)的出现, ...

  2. Python核心技术与实战——二一|巧用上下文管理器和with语句精简代码

    我们在Python中对于with的语句应该是不陌生的,特别是在文件的输入输出操作中,那在具体的使用过程中,是有什么引伸的含义呢?与之密切相关的上下文管理器(context manager)又是什么呢? ...

  3. kubernetes实战(二十八):Kubernetes一键式资源管理平台Ratel安装及使用

    1. Ratel是什么? Ratel是一个Kubernetes资源平台,基于管理Kubernetes的资源开发,可以管理Kubernetes的Deployment.DaemonSet.Stateful ...

  4. Python核心技术与实战——十九|一起看看Python全局解释器锁GIL

    我们在前面的几节课里讲了Python的并发编程的特性,也了解了多线程编程.事实上,Python的多线程有一个非常重要的话题——GIL(Global Interpreter Lock).我们今天就来讲一 ...

  5. Python核心技术与实战 笔记

    基础篇 Jupyter Notebook 优点 整合所有的资源 交互性编程体验 零成本重现结果 实践站点 Jupyter 官方 Google Research 提供的 Colab 环境 安装 运行 列 ...

  6. Python核心技术与实战——六|异常处理

    和其他语言一样,Python中的异常处理是很重要的机制和代码规范. 一.错误与异常 通常来说程序中的错误分为两种,一种是语法错误,另一种是异常.首先要了解错误和异常的区别和联系. 语法错误比较容易理解 ...

  7. Python核心技术与实战——十二|Python的比较与拷贝

    我们在前面已经接触到了很多Python对象比较的例子,例如这样的 a = b = a == b 或者是将一个对象进行拷贝 l1 = [,,,,] l2 = l1 l3 = list(l1) 那么现在试 ...

  8. Python核心技术与实战——十六|Python协程

    我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...

  9. Python核心技术与实战——十八|Python并发编程之Asyncio

    我们在上一章学习了Python并发编程的一种实现方法——多线程.今天,我们趁热打铁,看看Python并发编程的另一种实现方式——Asyncio.和前面协程的那章不太一样,这节课我们更加注重原理的理解. ...

随机推荐

  1. cpu切换线程上下文会耗费多少时间

    cpu切换线程上下文会耗费多少时间,有人在linux下面使用不同的cpu测试过,需要1000ns以上的时间 https://blog.tsunanet.net/2010/11/how-long-doe ...

  2. django在style的样式image url添加静态图片路径和django如何动态传入图片链接?

    #django在style的样式image url添加静态图片路径 style=" background:url({% static "agribusiness/images/lo ...

  3. 人工智能06 能计划的agent

    能计划的agent 存储与计算 响应agent的动作功能几乎没有做任何计算.从本质上讲,这些agent执行的动作或者由他们的设计者.或者通过学习.或者通过演化过程.或者由以上几方面的组合来选择给他们的 ...

  4. 【Python开发】增强的格式化字符串format函数

    自python2.6开始,新增了一种格式化字符串的函数str.format(),可谓威力十足.那么,他跟之前的%型格式化字符串相比,有什么优越的存在呢?让我们来揭开它羞答答的面纱. 语法 它通过{}和 ...

  5. 【Linux开发】linux设备驱动归纳总结(五):3.操作硬件——IO静态映射

    linux设备驱动归纳总结(五):3.操作硬件--IO静态映射 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  6. ElasticSearch - activemq - tomcat 开机自启动

    [root@qwy ~]# cat /etc/init.d/elastisearch|grep -v '^#' export JAVA_HOME=/usr/local/java/jdk1..0_172 ...

  7. MVC与MTV模型及Django请求的生命周期

    MVC模型 MVC:Model View Controller M: 模型.是应用程序中用于处理应用程序数据逻辑的部分 V:视图.是应用程序汇总处理数据显示的部分 C:控制器.是应用程序中处理用户交互 ...

  8. java-selenium三种等待方式

    方式1: 线程等待:Thread.sleep(xxxx) 只要在case中加入sleep就会强制等待设置的时间后才会执行之后的命令,这种等待一般适用于调试脚本的时候. java代码 //等待3秒 Th ...

  9. SVN服务器搭建与配置管理

    1.下载和搭建SVN服务器 现在Subversion已经迁移到Apache网站上了,下载地址:http://subversion.apache.org/packages.html,这是二进制文件包的下 ...

  10. DataX操作指南

    1.DataX介绍 DataX DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL.Oracle.SqlServer.Postgre.HDFS.Hive.ADS.HB ...