30

我经常使用 python 的 assert 语句来检查用户输入并在我们处于损坏状态时快速失败。我知道当 python 带有-o(优化的)标志时,断言会被删除。我个人不会在优化模式下运行我的任何应用程序,但感觉我应该远离断言以防万一。

写起来感觉干净多了

assert filename.endswith('.jpg')

if not filename.endswith('.jpg'):
    raise RuntimeError

这是断言的有效用例吗?如果不是,pythonassert语句的有效用例是什么?

4

8 回答 8

26

断言应该用于表达不变量前提条件
在您的示例中,您使用它们来检查意外输入 - 这是完全不同的异常类别

根据要求,在错误输入时引发异常并停止应用程序可能是完全可以的;然而,代码应该始终针对表达性进行定制,并且提高 anAssertionError并不是那么明确。
更好的办法是提出你自己的异常,或者一个ValueError.

于 2010-01-26T20:23:51.417 回答
18

如果优雅是不可能的,那就戏剧化

这是您的代码的正确版本:

if filename.endswith('.jpg'):
    # convert it to the PNG we want
    try:
        filename = convert_jpg_to_png_tmpfile(filename)
    except PNGCreateError:
        # Tell the user their jpg was crap
        print "Your jpg was crap!"

这是一个有效的案例,恕我直言,当:

  1. 这个错误完全是 100%致命的,处理它太可怕了,无法理解
  2. 只有当某些事情导致逻辑定律发生变化时,断言才会失败

否则,应对不测事件,因为你可以预见它的到来。

ASSERT == "这不应该在现实中发生,如果发生,我们放弃"

当然,这不一样

#control should never get here

但我总是这样做

#control should never get here
#but i'm not 100% putting my money where my mouth
#is
assert(False)

这样我得到一个很好的错误。在您的示例中,我将使用该if版本并将文件转换为 jpg!

于 2010-01-26T20:03:57.007 回答
9

完全有效。断言是关于程序状态的正式声明。

您应该将它们用于无法证明的事情。但是,对于您认为可以证明您的逻辑检查的事情,它们很方便。

另一个例子。

def fastExp( a, b ):
    assert isinstance(b,(int,long)), "This algorithm raises to an integer power"
    etc.

完后还有。最后的断言有点傻,因为它应该是可证明的。

# Not provable, essential.
assert len(someList) > 0, "Can't work with an empty list."
x = someList[]
while len(x) != 0:
    startingSize= len(x)
    ... some processing ...
    # Provable.  May be Redundant.
    assert len(x) < startingSize, "Design Flaw of the worst kind."

完后还有。

def sqrt( x ):
    # This may not be provable and is essential.
    assert x >= 0, "Can't cope with non-positive numbers"
    ...
    # This is provable and may be redundant.
    assert abs( n*n - x ) < 0.00001 
    return n

做出正式断言的原因有很多。

于 2010-01-26T20:26:59.917 回答
6

assert最适合用于在测试期间应该处于活动状态的代码,当您肯定要在没有的情况下运行时-o

您个人可能永远不会运行,-o但是如果您的代码最终在一个更大的系统中并且管理员想要运行它会发生-o什么?

系统可能看起来运行良好,但有一些细微的错误已通过运行打开-o

于 2010-01-26T20:23:26.353 回答
4

就个人而言,我assert用于意外错误,或者您不希望在现实世界使用中发生的事情。每当处理来自用户或文件的输入时,都应该使用异常,因为它们可以被捕获并且您可以告诉用户“嘿,我期待一个 .jpg 文件!!”

于 2010-01-26T20:06:16.063 回答
2

Python Wiki 有一个关于有效使用断言的很好的指南

上面的答案没有必要在运行 Python 时澄清 -O 反对意见。引用以上页面:

如果 Python 是使用 -O 选项启动的,那么断言将被剥离并且不被评估。

于 2013-10-04T15:12:28.587 回答
1

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 的列表索引。所有这些步骤实际上可能没有任何错误地完成,但是在测试结束时我们有错误的数据,我们不知道为什么。

那为什么要断言呢?当我们在测试和证明我们的合约时,如果有一些东西可以让我们获得更接近故障的调试信息,那就太好了。这与第一种形式几乎完全相同 - 毕竟,它进行完全相同的比较,但它在语法上更简洁,并且更专门用于验证合同。一个附带的好处是,一旦您合理地确定您的程序可以正常工作,并且正在优化并寻求更高的性能与可调试性,所有这些现在冗余的检查都可以删除。

于 2013-10-04T16:00:12.873 回答
0

底线:

  • assert它的语义是早期语言的遗产。
  • 事实证明,Python 的重心转变使传统用例变得无关紧要。
  • 在撰写本文时,尚未正式提出其他用例,尽管有一些想法可以将其改造成通用检查。
  • 如果您对限制没有问题,它可以像现在一样用作(并且确实被使用)作为通用检查。

ASSERT最初创建语句的预期用例是:

  • 进行相关但被认为对生产使用来说过于昂贵或不必要的健全性检查。
    • 例如,这些可以是对内部函数的输入检查,free()传递给先前获得的指针的malloc()检查等,对它们进行非平凡操作后的内部结构完整性检查等。

这在过去是一件大事,并且仍然存在于为性能而设计的环境中。这就是它在 C++/C# 中的语义的全部原因:

  1. 在发布版本中被删减
  2. 立即、不雅地、尽可能大声地终止程序。

然而,Python 有意识地和有意地牺牲代码性能来提高程序员的性能(信不信由你,我最近通过将一些代码从 Python 移植到 Cython 获得了 100 倍的加速 - 甚至没有禁用边界检查!)。Python 代码在“安全”环境中运行,因为您无法将进程(或整个系统)完全“破坏”为无法跟踪的段错误/BSoD/砖块——最糟糕的是,您会遇到带有大量调试信息的未处理异常附上,所有内容都以可读的形式优雅地呈现给您。

  • 这意味着运行时和库代码应该在适当的时候包括各种检查 - 在任何时候,无论它是否是“调试模式”。

此外,Python 对始终提供源代码的强大影响(透明编译、追溯中的源代码行、期望它们与.pyc's 一起使用的库存调试器)非常模糊了“开发”和“使用”之间的界限(这就是为什么setuptools' self-contained .eggs会引起强烈反对的原因之一-以及为什么 pip 总是将它们解包安装:如果安装的是打包的,则源不再容易获得并且存在问题 - 可诊断)。

结合起来,这些属性几乎破坏了“仅调试”代码的任何用例。

而且,你猜对了,一个将重新assert用作通用检查的想法最终浮出水面

于 2016-11-12T03:06:27.810 回答