让Python的“assert”抛出我选择的异常

58

我能否使assert抛出我选择的异常,而不是AssertionError

更新:

我来解释一下我的动机:到目前为止,我有一些类似于断言的测试,会引发我自己的异常;例如,当您使用某些参数创建一个Node对象时,它会检查这些参数是否适合创建节点,如果不适合,则会引发NodeError

但我知道Python有一个-o模式,可以跳过asserts,这让我的程序运行更快。但我仍然想要使用自己的异常。这就是我想要在assert中使用自己异常的原因。


好的,谢谢您提供额外的信息。为什么您想要使用自己的异常? - Ned Batchelder
当创建一个节点出现问题时,这通常是由于节点的逻辑出现了问题,因此我认为引发NodeError异常是合理的。 - Ram Rachum
1
Python的风格是在这种情况下使用更多内置的异常。无论如何,您都不应该捕获AssertionError,因此您不需要与调用者协调代码。我建议只需使用AssertionError并随之而来。 - Ned Batchelder
8个回答

66

这个方法可以使用。但有点太疯狂了。

try:
    assert False, "A Message"
except AssertionError, e:
    raise Exception( e.args )

为什么不用以下代码?这样会更加简洁。

if not someAssertion: raise Exception( "Some Message" )

它只比assert语句多了一点词,但不违反我们对于断言失败应该抛出AssertionError的期望。

考虑这个。

def myAssert( condition, action ):
    if not condition: raise action

那么你可以大致用类似于这样的内容替换现有的断言。

myAssert( {{ the original condition }}, MyException( {{ the original message }} ) )

一旦你完成了这个步骤,你现在可以自由地处理启用或禁用或其他你想要做的事情。

此外,阅读有关警告模块的内容。这可能正是你想要做的事情。


1
我想要能够享受-o选项。如果尝试不会造成太多开销,你的疯狂解决方案可能还可以。 - Ram Rachum
1
要检查-o选项,您可以简单地说if __debug__ and not someAssertion: - Ber
1
我认为try确实有开销。你可以自己试一下:https://dev59.com/PXI_5IYBdhLWcg3wAeHl#1569618 - John La Rooy
2
“try” 至少有几个操作码的开销 - “SETUP_EXCEPT”,“JUMP_FORWARD”/“JUMP_ABSOLUTE” 和 “POP_BLOCK”。 - Nick Bastin
在我了解如何正确使用assert()之前,我编写了自己的“xassert”,它会抛出AssertionError。这个想法是一样的,只是抛出自己的断言。+1 for myAssert()。 - David Poole
显示剩余3条评论

28

这个怎么样?


>>> def myraise(e): raise e
... 
>>> cond=False
>>> assert cond or myraise(RuntimeError)
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 1, in myraise
RuntimeError


2
这既美丽又可怕。 - user3576467

16

不要将断言用于逻辑!只用于可选的测试检查。请记住,如果Python正在运行时开启了优化,那么断言甚至不会编译成字节码。如果您这样做,显然您关心异常是否被引发,如果您关心的话,那么首先您就错误地使用了断言。


2
这样至少能让“优化”发挥作用。谁会使用优化呢?一开始似乎毫无意义;但是使用更多的断言至少给你提供了更多的优化选择,所以很有趣。 - u0b34a0f6ae

11

当使用-o选项运行Python时,Python也会跳过if __debug__:块。以下代码更加冗长,但可以在不使用hack的情况下完成所需操作:

def my_assert(condition, message=None):
    if not condition:
        raise MyAssertError(message)

if __debug__: my_assert(condition, message)

你可以通过将 if __debug__: 条件语句放在 my_assert() 函数内部来缩短它,但这样当开启编译器优化时,该条件语句仍然会被调用(而且没有任何操作)。


谢谢,我不知道 debug 是这样工作的。我已经将其添加到我的测试用例中。然而,目前得票最多的答案确实有一些额外开销。 - John La Rooy

6
您可以在with块中使用上下文管理器(可能包含多个断言、更多代码和函数调用或其他内容)来为您执行转换。请参考上下文管理器文档
from __future__ import with_statement
import contextlib

@contextlib.contextmanager
def myassert(exctype):
    try:
        yield
    except AssertionError, exc:
        raise exctype(*exc.args)

with myassert(ValueError):
    assert 0, "Zero is bad for you"

查看此答案的早期版本,可以直接替换构造的异常对象(KeyError("bad key")),而不是重复使用断言的参数。


4
如果你想使用断言来实现这一点,下面的方法似乎非常有效:
>>> def raise_(e): raise e
...
>>> x = -2
>>> assert x >= 0, raise_(ValueError('oops'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in raise_
ValueError: oops

请注意,在assert语句中,逗号后面的内容仅在条件为false时才会评估,因此只有在需要时才会创建并引发ValueError。

最佳答案,正是问题所需。在我看来,raise_ 是不必要的,assert x >= 0, raise ValueError('oops') 看起来一样,但没有函数调用开销。 - YoavBZ
@YoavBZ 你的代码在 3.8.0 上给了我一个语法错误。据我所知,语法需要逗号后面的东西是一个表达式,比如 raise_(...),而不是像 raise ... 那样的语句,这就是为什么我进行了定义 :) - uryga
抓住了,写之前应该检查一下 ;) - YoavBZ

4
在至少Python 2.6.3版本中,这也可以工作:
class MyAssertionError (Exception):
    pass

AssertionError = MyAssertionError

assert False, "False"

Traceback (most recent call last):
  File "assert.py", line 8, in <module>
    assert False, "False"
__main__.MyAssertionError: False

实际上,对我来说这看起来还不错,尽管它只在全局级别起作用。你不能在函数内部分配AssertionError并让assert语句引发你的异常。 - Ned Batchelder
将某物分配给AssertionError可能是唯一的方法。如果您认为自己有一个好的想法和语法,为什么不编写一个PEP呢? - John La Rooy
我最近能想到使用断言的唯一场合就是在单元测试中,这是它们的好去处。 - John La Rooy
我认为这非常有用——AssertionErrorException的子类,因此它可能会在except Exception中被意外地抑制。 - GingerPlusPlus

3
为了查看try是否有任何开销,我进行了以下实验。

here is myassert.py


def myassert(e):
    raise e

def f1(): #这是实验的控制 cond=True

def f2(): cond=True try: assert cond, "消息" except AssertionError, e: raise Exception(e.args)

def f3(): cond=True assert cond or myassert(RuntimeError)

def f4(): cond=True if __debug__: raise(RuntimeError)


$ python -O -mtimeit -n100 -r1000 -s'import myassert' 'myassert.f1()'
100 loops, best of 1000: 0.42 usec per loop
$ python -O -mtimeit -n100 -r1000 -s'import myassert' 'myassert.f2()'
100 loops, best of 1000: 0.479 usec per loop
$ python -O -mtimeit -n100 -r1000 -s'import myassert' 'myassert.f3()'
100 loops, best of 1000: 0.42 usec per loop
$ python -O -mtimeit -n100 -r1000 -s'import myassert' 'myassert.f4()'
100 loops, best of 1000: 0.42 usec per loop


1
尝试一下:...太慢了。但是你不应该用Python来追求速度。 - Erik Aronesty

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接