Python断言的最佳实践

时间:2009-06-03 12:57:17

标签: python assert assertion raise

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

    assert x >= 0, 'x is less than zero'
    

    好于或差于

    if x < 0:
        raise Exception, 'x is less than zero'
    
  2. 此外,是否有任何方法可以设置if x < 0 raise error之类的业务规则,在try/except/finally之后始终进行检查,以便在整个代码x中的任何时间都小于assert x < 0 0引发错误,就像在函数开头设置x一样,函数中{{1}}变得小于0的任何地方都会引发异常?

16 个答案:

答案 0 :(得分:690)

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

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


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


在您的示例中,如果x是通过用户界面或外部来源设置的值,则最好是例外。

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

答案 1 :(得分:333)

优化编译时删除

“assert”语句。所以,是的,有性能和功能差异。

  

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

如果您使用assert来实现应用程序功能,然后将部署优化到生产环境,那么您将受到“但是它在工作中开发”缺陷的困扰。

请参阅PYTHONOPTIMIZE-O -OO

答案 2 :(得分:137)

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

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

答案 3 :(得分:115)

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))
    

    这比文档中的等效信息更值得信赖 并且更容易维护。

  2. 通知计算机您的代码需要什么。
    assert强制执行代码调用者的正确行为。 如果您的代码调用Alices和Bernd的代码调用您的代码, 然后没有assert,如果程序在Alices代码中崩溃, 伯恩德可能认为这是爱丽丝的错, 爱丽丝调查并可能认为这是你的错, 你调查并告诉Bernd事实上他是他的。 失去了很多工作 有了断言,无论谁接到错误的电话,他们很快就能看到它 他们的错,不是你的。爱丽丝,伯恩德,你们都受益匪浅。 节省了大量的时间。

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

    assert(all(entry.isClean() for entry in mylist))
    

    这句话让每个试图理解的人都头疼 什么完全是美妙的循环正在实现。 而这些人中最常见的可能就是你自己。

  4. 告知计算机您的代码在某些时候取得的成就。
    你是否曾经忘记在小跑后调整需要它的入口, assert将节省您的一天并避免使用您的代码 亲爱的达芙妮很晚才打破。

  5. 在我看来,assert有两个文档目的(1和3)和 保障(2和4)同样有价值 告知人们甚至可能更多比通知计算机更有价值 因为它可以防止assert旨在捕获的非常错误(在案例1中) 在任何情况下都会出现大量后续错误。

答案 4 :(得分:20)

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

答案 5 :(得分:18)

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

class XLessThanZeroException(Exception):
    pass

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

def foo(x):
    CheckX(x)
    #do stuff here

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

答案 6 :(得分: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

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

答案 7 :(得分:6)

此处使用的英语单词 断言 用于 发誓 肯定 avow 。这并不意味着“check”“应该是”。这意味着作为一名程序员在这里制作 宣誓声明

# 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

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

答案 8 :(得分:3)

是否存在性能问题?

  • 请记住“让它快速运行之前先让它工作”
    任何程序的百分之几通常与其速度相关。 如果证明有assert,您可以随时踢出或简化def mymethod(listOfTuples): assert(all(type(tp)==tuple for tp in listOfTuples)) 是一个性能问题 - 而且大部分都不会。

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

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

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

    {{1}}

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

答案 9 :(得分:1)

有一个名为JBoss Drools的框架用于执行运行时监控以断言业务规则的java,它回答了问题的第二部分。但是,我不确定是否有这样的python框架。

答案 10 :(得分:1)

断言是检查 -
1。有效条件,
2.有效声明,
3.真实逻辑;
源代码。它不会使整个项目失败,而是会在源文件中发出不适合的警报。

在例1中,因为变量'str'不是nul。因此,不会引发任何断言或异常。

示例1:

let date = Date()
let hour = Calendar.current.component(.hour, from: date)

在示例2中,var'str'是nul。因此,我们通过断言语句来保护用户免于错误的程序。

示例2:

#!/usr/bin/python

str = 'hello Pyhton!'
strNull = 'string is Null'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
hello Pyhton!
FileName ..................... hello
FilePath ..................... C:/Python\hello.py

我们不想调试并在源代码中实现断言问题。禁用优化标志

python -O assertStatement.py
什么都不会打印

答案 11 :(得分:1)

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

目的

向初学者解释它-断言是可以引发错误的语句,但是您不会发现它们。而且通常不应该将它们提高,但是在现实生活中,无论如何它们有时都会得到提高。这是一种严重的情况,代码无法从中恢复,我们称之为“致命错误”。

接下来,它是出于“调试目的”,虽然正确,但听起来很不屑一顾。我更喜欢“声明不变式,永远不应该被违反”的表述,尽管它在不同的初学者中的工作方式有所不同……有些“只懂它”,而另一些要么找不到用处,要么替换正常的异常,甚至用它控制流程。

样式

在Python中,assert是语句,而不是函数! (请记住,assert(False, 'is true')不会加注。但是,请注意:

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

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

在一次性代码中,您可以执行以下操作而不会出现错误消息。

在某些情况下,没有要添加的断言:

def dump(东西):        断言isinstance(某些东西,可转储)        #...

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

提供信息,而不仅仅是泄漏内部实施细节。

代替:

assert meaningless_identifier <= MAGIC_NUMBER_XXX, 'meaningless_identifier is greater than MAGIC_NUMBER_XXX!!!'

写:

assert meaningless_identifier > MAGIC_NUMBER_XXX, 'reactor temperature above critical threshold'

甚至:

assert meaningless_identifier > MAGIC_NUMBER_XXX, f'reactor temperature({meaningless_identifier }) above critical threshold ({MAGIC_NUMBER_XXX})'

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

否定或肯定的消息?

这可能是肯定的,但是读类似这样的内容对我来说是个伤害

assert a == b, 'a is not equal to b'
  • 这是彼此矛盾的两件事。因此,只要我对代码库有影响,我就会通过使用诸如“必须”和“应该”之类的多余动词来推动我们想要的内容,而不是说我们不想要的内容。

    断言a == b,“ a必须等于b”

然后,获取AssertionError: a must be equal to b也是可读的,并且该语句在代码中看起来合乎逻辑。另外,您可以从中获取某些信息而无需阅读回溯(有时甚至不可用)。

答案 12 :(得分:1)

使用assert和引发异常都与沟通有关。

  • 断言是关于代码在开发人员中解决的正确性的声明:代码中的断言将正确的代码必须满足的条件通知给代码的读者。在运行时失败的断言通知开发人员代码中有需要修复的缺陷。

  • 异常是关于非典型情况的指示,这些非典型情况可能在运行时发生,但不能由当前代码解决,请在此处处理的调用代码处解决。发生异常并不表示代码中存在错误。

最佳做法

因此,如果您将运行时发生的特定情况视为要通知开发人员的错误(“开发人员,此情况表明某处存在错误,请修复该代码。 “)然后进行断言。如果断言检查代码的输入参数,则通常应在输入参数违反条件时向文档添加代码具有“未定义行为”的代码。

如果恰恰是这种情况的发生并不是您眼中的错误的迹象,而是您认为应该由客户端代码处理的(可能很少见但)可能的情况,请引发异常。引发异常的情况应该是相应代码的文档的一部分。

使用assert时是否存在性能问题

断言的评估需要一些时间。不过,可以在编译时将其消除。但是,这会带来一些后果。

使用assert时是否存在代码维护问题

通常,断言可以提高代码的可维护性,因为它们可以通过使假设明确化并在运行时定期验证这些假设来提高可读性。这也将有助于捕获回归。但是,需要牢记一个问题:断言中使用的表达式应该没有副作用。如上所述,可以在编译时消除断言-这意味着潜在的副作用也将消失。这可以-意外地-更改代码的行为。

答案 13 :(得分:0)

在诸如PTVS,PyCharm之类的IDE中,Wing assert isinstance()语句可用于为某些不清楚的对象启用代码完成。

答案 14 :(得分:0)

我要补充的是,我经常使用 assert 来指定诸如 loop invariants 之类的属性或我的代码应该具有的逻辑属性,就像我在经过正式验证的软件中指定它们一样。

它们既可以告知读者,也可以帮助我推理,并检查我的推理是否有误。例如:

k = 0
for i in range(n):
    assert k == i * (i + 1) // 2
    k += i 
    #do some things      

或更复杂的情况:

def sorted(l):
   return all(l1 <= l2 for l1, l2 in zip(l, l[1:]))
 
def mergesort(l):
   if len(l) < 2: #python 3.10 will have match - case for this instead of checking length
      return l
   k = len(l // 2)
   l1 = mergesort(l[:k])
   l2 = mergesort(l[k:])
   assert sorted(l1) # here the asserts allow me to explicit what properties my code should have
   assert sorted(l2) # I expect them to be disabled in a production build
   return merge(l1, l2)

由于当 python 在优化模式下运行时断言被禁用,请不要犹豫在其中编写昂贵的条件,特别是如果它使您的代码更清晰并且不易出错

答案 15 :(得分:-2)

如果你正在处理依赖于assert正常运行的遗留代码,即使it should not,那么添加以下代码是一个快速修复,直到你找到时间重构:

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