首页 文章

Python断言的最佳实践

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

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

好或坏

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

13 回答

  • 1

    当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
    
  • 1

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

    异常应该用于可能发生的错误,并且 you should almost always create your own Exception classes .


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


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

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

  • 7

    "assert" statements are removed when the compilation is optimized . 所以,是的,有性能和功能差异 .

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

    如果您使用 assert 来实现应用程序功能,然后将部署优化到 生产环境 ,您将受到"but-it-works-in-dev"缺陷的困扰 .

    PYTHONOPTIMIZE-O -OO

  • 133

    断言的四个目的

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

    然后 assertfour roles

    • Inform Alice, Bernd, Carl, and Daphne what your code expects.
      假设您有一个处理元组列表的方法,如果这些元组不是不可变的,程序逻辑可能会中断:
    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))
    

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

    • Inform the computer what your code expects.
      assert 强制执行代码调用者的正确行为 . 如果你的代码调用Alices 's and Bernd' s代码调用你的,那么没有 assert ,如果程序在Alices代码崩溃,Bernd可能会认为这是Alice的错,Alice调查并可能认为这是你的错,你调查并告诉Bernd它在事实上他的 . 失去了很多工作 .
      有了断言,无论谁接到错误的电话,他们很快就会发现这是他们的错,而不是你的错 . 爱丽丝,伯恩德,你们都受益匪浅 . 节省了大量的时间 .

    • Inform the readers of your code (including yourself) what your code has achieved at some point.
      假设你有一个条目列表,每个条目都可以是干净的(这很好),或者它可以是smorsh,trale,gullup或者闪烁(这些都是不可接受的) . 如果它必须是baludo;它必须是baludoed;如果它闪烁,它必须再次闪烁,除了星期四 . 你明白这个想法:这很复杂 . 但最终结果是(或应该)所有条目都是干净的 . Right Thing(TM)要做的是总结清洁循环的效果

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

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

    • Inform the computer what your code has achieved at some point.
      如果您在小跑之后忘记调整需要它的条目, assert 将节省您的一天,并避免您的代码打破亲爱的Daphne's晚了 .

    在我看来,文件(1和3)和保障(2和4)的两个目的同样有 Value .
    告知人们甚至可能比告知计算机更有 Value ,因为它可以防止出现的错误(案例1)以及随后的大量错误 .

  • -1

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

  • 654

    _606519很难用assert语句做出非常具有描述性的异常 . 如果您正在寻找更简单的语法,请记住您也可以这样做:

    class XLessThanZeroException(Exception):
        pass
    
    def CheckX(x):
        if x < 0:
            raise XLessThanZeroException()
    
    def foo(x):
        CheckX(x)
        #do stuff here
    

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

  • 18

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

    例如,递归函数(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
    

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

  • 0

    英语单词 assert 在这里用于 swearaffirmavow . 这并不意味着"check"或"should be" . 这意味着你作为一名程序员在这里制作一个 sworn statement

    # 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,硬件故障等, no assert will ever fail . 这就是为什么程序对最终用户的行为不得受到影响的原因 . 特别是,即使在 exceptional programmatic conditions 下,断言也不会失败 . 它永远不会发生 . 如果它发生了,那么程序员应该为之奋斗 .

  • 112

    是否存在性能问题?

    • 请记住 "make it work first before you make it work fast" .
      任何程序的百分之几通常与其速度相关 . 如果它被证明是一个性能问题,你总是可以踢出或简化 assert - 而且大部分都不会 .

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

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))
    

    如果您的列表往往长达十个条目,这可能很好,但如果它们有一百万个条目,它可能会成为一个问题 . 但不是完全丢弃这个有 Value 的支票,你可以简单地将其降级为

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

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

  • 3

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

  • 321

    断言是检查 -
    1. the valid condition, 2. the valid statement, 3. true logic; 源代码 . 它不会使整个项目失败,而是会在源文件中发出不适合的警报 .

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

    Example 1:

    #!/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
    

    在示例2中,var 'str'是nul . 所以我们通过 assert 语句来保存用户免于错误的程序 .

    Example 2:

    #!/usr/bin/python
    
    str = ''
    strNull = 'NULL String'
    
    if __debug__:
        if not str: raise AssertionError(strNull)
    print str
    
    if __debug__:
        print 'FileName '.ljust(30,'.'),(__name__)
        print 'FilePath '.ljust(30,'.'),(__file__)
    
    
    ------------------------------------------------------
    
    Output:
    AssertionError: NULL String
    

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

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

  • 19

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

  • 3

    如果您正在处理依赖 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
    

相关问题