S.Lott的回答是最好的。但这太长了,只是在他的评论中添加,所以我把它放在这里。无论如何,这就是我对assert的看法,基本上它只是一种缩写方式,用于执行#ifdef DEBUG。
无论如何,有两种关于输入检查的思路。你可以在目标处进行,也可以在源代码处进行。
在目标处进行是在代码内部进行的:
def sqrt(x):
if x<0:
raise ValueError, 'sqrt requires positive numbers'
root = <do stuff>
return root
def some_func(x):
y = float(raw_input('Type a number:'))
try:
print 'Square root is %f'%sqrt(y)
except ValueError:
print '%f must be a positive number!'%y
现在,这有很多优点。可能编写sqrt函数的人最了解算法中哪些是有效值。在上述示例中,我不知道从用户那里得到的值是否有效。必须有人进行检查,并且在最了解什么是有效的代码 - sqrt算法本身中进行检查是有意义的。
然而,这会带来性能损失。想象一下像这样的代码:
def sqrt(x):
if x<=0:
raise ValueError, 'sqrt requires positive numbers'
root = <do stuff>
return root
def some_func(maxx=100000):
all_sqrts = [sqrt(x) for x in range(maxx)]
i = sqrt(-1.0)
return(all_sqrts)
现在,这个函数会调用sqrt 100k次。每次,sqrt都会检查值是否大于等于0。但是,由于我们如何生成这些数字,我们已经知道它们是有效的,那些额外的有效性检查只是浪费执行时间。如果能摆脱它们不好吗?然后还有一个将引发ValueError的函数,我们将捕获它并意识到我们犯了一个错误。我编写程序依赖于子函数来检查我,所以当它不起作用时,我只需要担心恢复。
第二种思路是,不是目标函数检查输入,而是在定义中添加约束,并要求调用者确保使用有效数据进行调用。该函数承诺使用良好的数据将返回其合同规定的内容。这样可以避免所有这些检查,因为调用者比目标函数更了解发送的数据,它来自哪里以及固有的约束条件。这些的最终结果是代码契约和类似结构,但最初只是通过惯例,在设计中,就像下面的注释:
# This function valid if x > 0
def sqrt(x):
root = <do stuff>
return root
def long_function(maxx=100000):
# This is a valid function call - every x i pass to sqrt is valid
sqrtlist1 = [sqrt(x) for x in range(maxx)]
# This one is a program error - calling function with incorrect arguments
# But one that can't be statically determined
# It will throw an exception somewhere in the sqrt code above
i = sqrt(-1.0)
当然,错误总是难免的,合同可能会被违反。但到目前为止,结果基本相同 - 在两种情况下,如果我调用sqrt(-1.0),我将在sqrt代码内部收到异常,可以遍历异常堆栈,并找出我的错误所在。
然而,还有更加隐蔽的情况......例如,假设我的代码生成列表索引,存储它,稍后查找列表索引,提取值并进行某些处理。假设我们意外得到了一个-1的列表索引。所有这些步骤可能实际上都完成了而没有任何错误,但最终测试结果是错误的,我们不知道为什么。
那么为什么要使用assert?在测试和证明合同时,我们希望有一些东西能够让我们更接近失败的调试信息。这基本上与第一种形式完全相同 - 毕竟,它执行的是完全相同的比较,但它的语法更加整洁,稍微更专门用于验证合同。一个副作用是,一旦您相当确信您的程序工作正常,并且正在优化和寻找更高的性能而非可调试性,所有这些现在多余的检查都可以删除。