Python断言的最佳实践

mea*_*ade 452 python assert raise assertion

  1. assert作为标准代码的一部分使用而不是仅仅用于调试目的,是否存在性能或代码维护问题?

    assert x >= 0, 'x is less than zero'
    
    Run Code Online (Sandbox Code Playgroud)

    好或坏比

    if x < 0:
        raise Exception, 'x is less than zero'
    
    Run Code Online (Sandbox Code Playgroud)
  2. 另外,有没有办法设置业务规则if x < 0 raise error,总是在没有try/except/finally这样的情况下进行检查,如果在整个代码中的任何时候x小于0都会引发错误,就像你assert x < 0在函数的开头设置一样,在函数内的任何地方哪里x变得少于0则引发异常?

Dee*_*tan 713

应该使用断言来测试不应该发生的条件.目的是在程序状态损坏的情况下尽早崩溃.

异常应该用于可能发生的错误,并且您应该几乎总是创建自己的异常类.


例如,如果您正在编写一个函数来从配置文件读取到一个dict,则文件中的格式不正确应该会引发一个ConfigurationSyntaxError,而您可能assert不会返回None.


在您的示例中,if x是通过用户界面或外部源设置的值,最好是异常.

如果x仅在同一程序中由您自己的代码设置,请使用断言.

  • 这是使用断言的正确方法.它们不应该用于控制程序流程. (121认同)
  • 最后一段的+1 - 虽然你应该[明确](http://www.python.org/dev/peps/pep-0020/"明确比隐含更好.")提到[`assert`](http ://docs.python.org/2/reference/simple_stmts.html#assert)包含一个隐含的`if __debug__`,可能是[优化](http://docs.python.org/2/using/cmdline.html ?highlight = optimization%20options#envvar-PYTHONOPTIMIZE)离开 - 如[John Mee的回答](http://stackoverflow.com/a/1838411/321973)所述 (38认同)
  • 断言应该只用于捕捉没有已知恢复的问题; 几乎总是编码错误(不错的输入).当一个断言被触发时,它应该意味着程序处于可能继续存在危险的状态,因为它可能开始与网络通信或写入磁盘.强大的代码在恶意(或恶意)输入面前从原始状态"原子地"移动到有效状态.每个线程的顶层都应该有一个故障障碍.消耗来自外部世界的输入的故障障碍通常仅在屏障的一次迭代中失败(而/尝试),回滚/登录错误. (10认同)
  • "应该使用断言来测试不应该发生的条件." 是.第二个"应该"的含义是:如果发生这种情况,程序代码是不正确的. (9认同)
  • 重读你的答案我认为你可能并不意味着*条件应该永远不会发生*作为一项规则,而是*目的是在腐败程序状态的情况下提前崩溃,这通常与你不满意的情况一致期待永远不会发生*. (3认同)
  • 在我看来,你应该几乎不应该创建自己的异常类。如果您的代码将根据出错的情况采取不同的行为,那么您只关心异常的类型;这并不常见。90% 的情况下,您的代码只关心出了什么问题。调试问题的人关心出了什么问题,这就是异常消息的用途。 (3认同)
  • 这是对我最好的答案,但我错过了一些事情。如果使用-O字节编译时将删除`assert`,则`assert`不应该使IMHO程序崩溃。该程序应自行崩溃,或者断言。正如@LutzPrechelt在其回答中所说,“ assert”仅应用作提供信息的陈述。 (2认同)
  • 意识到这个答案最初是在 12 年前发布的,现在情况可能有所不同,我同意 Alex 的观点,你不应该_永远_编写自己的异常,除非你_真的_需要匹配几种不同类型的失败状态并且标准类型发生冲突,强制您对错误消息进行字符串匹配。如果您的代码在一个地方可能会以多种方式失败,那么您可能应该重写您的代码。 (2认同)

Joh*_*Mee 348

优化编译时,将删除"assert"语句.所以,是的,有性能和功能差异.

在编译时请求优化时,当前代码生成器不会为assert语句发出任何代码.- Python 2.6.4 Docs

如果您使用assert实现应用程序功能,然后优化部署到生产,您将受到"但它在工作中开发"缺陷的困扰.

参见PYTHONOPTIMIZE-O -​​OO

  • 哇!这是超级重要的注意事项!我一直在计划使用断言来检查一些永远不会失败的事情,这些事情的失败将表明有人非常小心地操纵我们发送的数据,试图获取他们无法访问的数据.它不会起作用,但是我想用一个断言迅速关闭它们的尝试,因此在生产中优化掉它会破坏目的.我想我只会"提高"一个"异常".哦 - 我刚刚在`Django`中发现了一个名为`SuspiciousOperation``Exception`的子类.完善! (24认同)

Nad*_*mli 139

当x在整个函数中变得小于零时能够自动抛出错误.您可以使用类描述符.这是一个例子:

class LessThanZeroException(Exception):
    pass

class variable(object):
    def __init__(self, value=0):
        self.__x = value

    def __set__(self, obj, value):
        if value < 0:
            raise LessThanZeroException('x is less than zero')

        self.__x  = value

    def __get__(self, obj, objType):
        return self.__x

class MyClass(object):
    x = variable()

>>> m = MyClass()
>>> m.x = 10
>>> m.x -= 20
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "my.py", line 7, in __set__
    raise LessThanZeroException('x is less than zero')
LessThanZeroException: x is less than zero
Run Code Online (Sandbox Code Playgroud)

  • 非常好的答案,喜欢它,但不得不对问题这样做......难道我们不能将Deestan或John Mee的回答标记为有效回答吗? (101认同)
  • 尽管属性是作为描述符实现的,但我不会将其称为使用它们的示例.这更像是一个属性的例子:http://docs.python.org/library/functions.html#property (10认同)
  • @VajkHermecz:实际上,如果你重读这个问题,这就是两个问题.只看标题的人只熟悉第一个问题,这个答案没有回答.这个答案实际上包含了第二个问题的答案. (9认同)
  • 设置x时,应在MyClass中使用这些属性.这个解决方案太笼统了. (3认同)
  • 这似乎没有回答问题的标题.此外,这是Python的类属性功能的不良替代品. (3认同)

Lut*_*elt 123

四个目的 assert

假设您与四位同事Alice,Bernd,Carl和Daphne一起处理了200,000行代码.他们调用你的代码,你调用他们的代码.

然后assert四个角色:

  1. 告知Alice,Bernd,Carl和Daphne您的代码所期望的内容.
    假设您有一个处理元组列表的方法,如果这些元组不是不可变的,程序逻辑可能会中断:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))
    
    Run Code Online (Sandbox Code Playgroud)

    这比文档中的等效信息更可靠,更易于维护.

  2. 通知计算机代码期望的内容.
    assert从代码的调用者强制执行适当的行为.如果你的代码调用Alices和Bernd的代码调用你的代码,那么没有assert,如果程序在Alices代码崩溃,Bernd可能会认为这是Alice的错,Alice调查并可能认为这是你的错,你调查并告诉Bernd它实际上是他的.失去了很多工作.
    有了断言,无论谁接到电话都是错误的,他们很快就会发现这是他们的错,而不是你的错.爱丽丝,伯恩德,你们都受益匪浅.节省了大量的时间.

  3. 告诉读者您的代码(包括您自己)在某些时候代码所取得的成就.
    假设你有一个条目列表,每个条目都可以是干净的(这很好),或者它可以是smorsh,trale,gullup或者闪烁(这些都是不可接受的).如果它是smorsh它必须是unmorshed; 如果它是trale它必须是baludoed; 如果它是gullup它必须小跑(然后也可能节奏); 如果它闪烁,它必须再次闪烁,除了星期四.你明白这个想法:这很复杂.但最终结果是(或应该)所有条目都是干净的.Right Thing(TM)要做的是总结清洁循环的效果

    assert(all(entry.isClean() for entry in mylist))
    
    Run Code Online (Sandbox Code Playgroud)

    对于每个试图理解精彩循环实现的确切内容的人来说,这个陈述让人头疼.而这些人中最常见的可能就是你自己.

  4. 告知计算机您的代码在某些时候已经实现了什么.
    你是否曾经忘记在小跑后调整需要它的条目,这assert将节省你的一天,并避免你的代码打破亲爱的达芙妮的后期.

在我看来,assert文档(1和3)和安全(2和4)的两个目的同样有价值.
告知人们甚至可能比告知计算机有价值,因为它可以防止assert目标捕获的错误(在案例1中)和任何情况下的大量后续错误.

  • 5.**assert isinstance()**帮助PyCharm(python IDE)知道变量的类型,它用于自动完成. (32认同)
  • 关于2和4:你应该非常小心,你的断言不是太严格.断言,断言本身可能是保持程序在更一般环境中使用的唯一方法.特别是断言类型与python的duck-typing相反. (9认同)
  • @Cjkjvfnby小心过度使用isinstance(),如本博客文章中所述:"[isinstance()被认为有害](http://canonical.org/~kragen/isinstance/)".您现在可以[在Pycharm中使用docstrings指定类型](https://www.jetbrains.com/pycharm/help/using-docstrings-to-specify-types.html). (9认同)
  • 断言自文档代码假设在当前执行时间为真。这是一个假设评论,它会被检查。 (2认同)
  • 以一种确保合同的方式使用断言.有关更多信息Design by Contract https://en.wikipedia.org/wiki/Design_by_contract (2认同)
  • 类型注释(或有时为“typing.cast”)是“assert isinstance”的更现代的替代方案。 (2认同)

out*_*tis 21

除了其他答案之外,断言本身会抛出异常,但只会抛出AssertionErrors.从功利主义的角度来看,当你需要对你捕获的异常进行细粒度控制时,断言是不适合的.

  • 对.在调用者中捕获断言错误异常似乎很愚蠢. (3认同)

Jas*_*ker 18

这种方法唯一真正错误的是使用assert语句很难做出非常具描述性的异常.如果您正在寻找更简单的语法,请记住您可以这样做:

class XLessThanZeroException(Exception):
    pass

def CheckX(x):
    if x < 0:
        raise XLessThanZeroException()

def foo(x):
    CheckX(x)
    #do stuff here
Run Code Online (Sandbox Code Playgroud)

另一个问题是使用assert进行正常的条件检查是因为它很难使用-O标志禁用调试断言.

  • 您可以向断言附加错误消息.这是第二个参数.这将使其具有描述性. (24认同)

Ant*_*ala 9

英语语言文字断言在这里的意义上使用发誓,肯定,出来的事.它并不意味着"检查""应该".这意味着作为一名程序员在这里发表宣誓声明:

# I solemnly swear that here I will tell the truth, the whole truth, 
# and nothing but the truth, under pains and penalties of perjury, so help me FSM
assert answer == 42
Run Code Online (Sandbox Code Playgroud)

如果代码是正确的,除了单事件干扰,硬件故障等,没有断言将失败.这就是为什么程序对最终用户的行为不得受到影响的原因.特别是,即使在特殊的程序条件下,断言也不会失败.它永远不会发生.如果它发生了,那么程序员应该为之奋斗.


mat*_*ts1 7

如前所述,当您的代码不应该达到某一点时,应该使用断言,这意味着存在错误.我可以看到使用断言的最有用的原因可能是一个不变/前/后条件.这些在循环或函数的每次迭代的开始或结束时必须是真的.

例如,一个递归函数(2个单独的函数,因此1处理错误的输入,另一个处理错误的代码,导致很难用递归区分).如果我忘记编写if语句,那会出现问题.

def SumToN(n):
    if n <= 0:
        raise ValueError, "N must be greater than or equal to 0"
    else:
        return RecursiveSum(n)

def RecursiveSum(n):
    #precondition: n >= 0
    assert(n >= 0)
    if n == 0:
        return 0
    return RecursiveSum(n - 1) + n
    #postcondition: returned sum of 1 to n
Run Code Online (Sandbox Code Playgroud)

这些循环不变量通常可以用断言表示.

  • 这最好用装饰器完成(@precondition和@postcondition) (2认同)

Emi*_*har 7

无论如何,如果您正在处理依赖于assert正常运行的代码,那么添加以下代码将确保启用断言:

try:
    assert False
    raise Exception('Python assertions are not working. This tool relies on Python assertions to do its job. Possible causes are running with the "-O" flag or running a precompiled (".pyo" or ".pyc") module.')
except AssertionError:
    pass
Run Code Online (Sandbox Code Playgroud)

  • 这并没有回答OP关于最佳实践的问题。 (4认同)

Lut*_*elt 5

是否存在性能问题?

  • 请记住“先使其发挥作用,然后才能使其快速发挥作用”
    任何程序中很少有一部分与其速度相关。如果事实证明它是一个性能问题,您总是可以将其排除或简化assert——而且大多数人永远不会这样做。

  • 务实一点
    假设您有一个处理非空元组列表的方法,并且如果这些元组不是不可变的,则程序逻辑将中断。你应该写:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))
    
    Run Code Online (Sandbox Code Playgroud)

    如果您的列表往往有十个条目,这可能没问题,但如果有一百万个条目,这可能会成为问题。但是,您不必完全放弃这张有价值的支票,只需将其降级为

    def mymethod(listOfTuples):
        assert(type(listOfTuples[0])==tuple)  # in fact _all_ must be tuples!
    
    Run Code Online (Sandbox Code Playgroud)

    这很便宜,但无论如何都可能捕获大多数实际的程序错误。

  • 应该是 `assert(len(listOfTuples)==0 或 type(listOfTyples[0])==tuple)`。 (2认同)

Tom*_*dor 5

嗯,这是一个悬而未决的问题,我有两个方面想谈谈:何时添加断言以及如何编写错误消息。

目的

向初学者解释一下 - 断言是可能引发错误的语句,但您不会发现它们。他们通常不应该被抚养,但在现实生活中,他们有时确实会被抚养。这是一种严重的情况,代码无法从中恢复,我们称之为“致命错误”。

接下来,它用于“调试目的”,虽然正确,但听起来很不屑一顾。我更喜欢“声明不应该被违反的不变量”公式,尽管它对不同的初学者有不同的作用......有些“只是明白”,而其他人要么找不到任何用处,要么替换正常的异常,甚至用它控制流量。

风格

在 Python 中,assert是一个语句,而不是一个函数!(记住assert(False, 'is true')不会加注。但是,把它放在一边:

何时以及如何编写可选的“错误消息”?

此acually适用于单元测试框架,其通常具有许多专用的方法来做断言(assertTrue(condition)assertFalse(condition), assertEqual(actual, expected)等)。它们通常还提供一种对断言进行评论的方法。

在一次性代码中,您可以在没有错误消息的情况下进行操作。

在某些情况下,断言无需添加任何内容:

def dump(something): assert isinstance(something, Dumpable) # ...

但除此之外,消息对于与其他程序员(有时是您的代码的交互式用户,例如在 Ipython/Jupyter 等中)进行通信很有用。

给他们信息,而不仅仅是泄露内部实现细节。

代替:

assert meaningless_identifier <= MAGIC_NUMBER_XXX, 'meaningless_identifier is greater than MAGIC_NUMBER_XXX!!!'
Run Code Online (Sandbox Code Playgroud)

写:

assert meaningless_identifier > MAGIC_NUMBER_XXX, 'reactor temperature above critical threshold'
Run Code Online (Sandbox Code Playgroud)

或者甚至:

assert meaningless_identifier > MAGIC_NUMBER_XXX, f'reactor temperature({meaningless_identifier }) above critical threshold ({MAGIC_NUMBER_XXX})'
Run Code Online (Sandbox Code Playgroud)

我知道,我知道 - 这不是静态断言的情况,但我想指出消息的信息价值。

消极或积极的信息?

这可能是有争议的,但阅读以下内容让我很受伤:

assert a == b, 'a is not equal to b'
Run Code Online (Sandbox Code Playgroud)
  • 这是两个相互矛盾的东西,并排在一起。因此,每当我对代码库产生影响时,我都会通过使用诸如“必须”和“应该”之类的额外动词来推动指定我们想要什么,而不是说我们不想要什么。

    assert a == b, 'a 必须等于 b'

然后,获取AssertionError: a must be equal to b也是可读的,并且该语句在代码中看起来很合乎逻辑。此外,您可以在不阅读回溯的情况下从中获得一些东西(有时甚至不可用)。