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:
# User did not type valid input
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 的列表索引。所有这些步骤实际上可能没有任何错误地完成,但是在测试结束时我们有错误的数据,我们不知道为什么。
那为什么要断言呢?当我们在测试和证明我们的合约时,如果有一些东西可以让我们获得更接近故障的调试信息,那就太好了。这与第一种形式几乎完全相同 - 毕竟,它进行完全相同的比较,但它在语法上更简洁,并且更专门用于验证合同。一个附带的好处是,一旦您合理地确定您的程序可以正常工作,并且正在优化并寻求更高的性能与可调试性,所有这些现在冗余的检查都可以删除。