问题:Python断言的最佳实践
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引发异常?
回答 0
为了能够在整个函数中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
回答 1
应该使用断言来测试永远不会发生的条件。目的是在程序状态损坏的情况下尽早崩溃。
应该将异常用于可能发生的错误,并且几乎应该始终创建自己的Exception类。
例如,如果您要编写一个从配置文件读取到的函数,则文件中dict
不正确的格式将引发a ConfigurationSyntaxError
,同时assert
您可以避免返回None
。
在您的示例中,如果x
是通过用户界面或外部来源设置的值,则最好是exceptions。
如果x
仅由您自己的代码在同一程序中设置,请声明。
回答 2
优化编译后,将删除“ assert”语句。因此,是的,在性能和功能上都存在差异。
当在编译时请求优化时,当前代码生成器不会为assert语句生成任何代码。- Python 2的文档 的Python 3文档
如果您用于assert
实现应用程序功能,然后优化对生产的部署,那么“ but-it-works-in-dev”缺陷将给您带来困扰。
回答 3
的四个目的 assert
假设您与四个同事Alice,Bernd,Carl和Daphne一起处理了200,000行代码。他们叫您的代码,您叫他们的代码。
然后assert
具有四个角色:
告知Alice,Bernd,Carl和Daphne您的代码期望什么。
假设您有一个处理元组列表的方法,并且如果这些元组不是不可变的,则程序逻辑可能会中断:def mymethod(listOfTuples): assert(all(type(tp)==tuple for tp in listOfTuples))
比文档中的等效信息更值得信赖,并且更易于维护。
通知计算机您的代码期望什么。
assert
强制代码调用者采取适当的行为。如果您的代码调用了Alices的代码,而Bernd的代码调用了您的代码,则没有assert
,如果程序在Alices代码中崩溃,Bernd可能认为这是Alice的错误,Alice进行了调查,并可能认为这是您的错误,您调查了并告诉Bernd实际上他的。很多工作丢失了。
有了断言,无论谁打错电话,他们都将能够迅速看到这是他们的错,而不是您的错。爱丽丝,伯恩德,你们都将从中受益。节省大量时间。通知您的代码(包括您自己)的读者在某些时候取得了什么成就。
假设您有一个条目列表,并且每个条目都可以是干净的(很好),也可以是乱码,流浪汉,gullup或闪烁的(都不可接受)。如果它很轻便,必须将其清零。如果是流浪汉,则必须加以保护;如果它是古怪的,则必须将其放小(然后也可能要加快速度);如果已闪烁,则必须再次闪烁(星期四除外)。您会明白:这是复杂的东西。但是最终结果是(或应该是)所有条目都是干净的。Right Thing(TM)要做的是将清洁循环的效果总结为assert(all(entry.isClean() for entry in mylist))
该报表节省了大家试图了解头痛究竟它是一个美妙的循环实现。这些人中最常出现的人可能就是你自己。
通知计算机您的代码在某些时候已经实现了什么。
如果您在小跑后忘记了需要它的条目,assert
它将节省您的时间,并避免您的代码在以后很长时间内破坏亲爱的达芙妮。
在我看来,assert
文档的两个目的(1和3)和保障(2和4)同等重要。
通知人们甚至比通知计算机更有价值,因为通知人们可以防止assert
目标要抓住的错误(在情况1中)以及在任何情况下都可以避免许多后续错误。
回答 4
除了其他答案外,断言本身会引发异常,但仅断言AssertionErrors。从功利主义的角度来看,断言不适用于需要精细控制所捕获的异常的情况。
回答 5
这种方法唯一真正出错的地方是,使用assert语句很难创建非常描述性的异常。如果您正在寻找更简单的语法,请记住您也可以执行以下操作:
class XLessThanZeroException(Exception):
pass
def CheckX(x):
if x < 0:
raise XLessThanZeroException()
def foo(x):
CheckX(x)
#do stuff here
另一个问题是,使用assert进行正常的条件检查是,使用-O标志很难禁用调试声明。
回答 6
英语语言文字断言在这里的意义上使用发誓,申明,招认。这并不意味着“检查”或“应该”。这意味着您作为编码人员正在此处宣誓就职:
# 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
如果代码正确,则除非发生单事件失败,硬件故障等,否则断言不会失败。这就是为什么不得影响最终用户的程序行为。特别是,断言即使在特殊的程序条件下也不会失败。只是从来没有发生过。如果发生这种情况,程序员应该对此进行调整。
回答 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
这些循环不变式通常可以用断言来表示。
回答 8
是否存在性能问题?
请记住“先使其工作,然后再使其快速工作”。
通常,几乎没有任何程序的百分比与其速度有关。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!
这很便宜,但无论如何都会捕获大多数实际程序错误。
回答 9
好吧,这是一个悬而未决的问题,我想谈谈两个方面:何时添加断言以及如何编写错误消息。
目的
向初学者解释它-断言是可能引发错误的语句,但是您不会抓住它们。而且通常不应该将它们提高,但是在现实生活中,无论如何它们有时都会得到提高。这是一种严重的情况,代码无法从中恢复,我们称之为“致命错误”。
接下来,它是出于“调试目的”,虽然正确,但听起来很不屑一顾。我更喜欢“声明不变式,永远不应该被违反”的表述,尽管它在不同的初学者中的工作方式有所不同……有些“只懂它”,而另一些要么找不到用处,要么替换正常的异常,甚至用它控制流程。
样式
在Python中,assert
它是语句,而不是函数!(请记住assert(False, 'is true')
不会提高。但是,请注意:
何时以及如何编写可选的“错误消息”?
此acually适用于单元测试框架,其通常具有许多专用的方法来做断言(assertTrue(condition)
,assertFalse(condition), assertEqual(actual, expected)
等)。它们通常还提供一种对断言进行评论的方法。
在一次性代码中,您可以不显示错误消息。
在某些情况下,没有要添加的断言:
def dump(something):断言isinstance(something,Dumpable)#…
但是除此之外,一条消息对于与其他程序员(有时是代码的交互用户,例如在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
也是可读的,并且该语句在代码中看起来合乎逻辑。另外,您可以从中获得某些东西而无需阅读回溯(有时甚至不可用)。
回答 10
assert
异常的使用和引发都与沟通有关。
断言是关于开发人员要解决的代码正确性的声明:代码中的断言将代码的正确性告知读者,有关正确代码必须满足的条件。在运行时失败的断言通知开发人员代码中存在需要修复的缺陷。
异常是关于非典型情况的指示,这些非典型情况可能在运行时发生,但不能被手头的代码解决,请在此处处理的调用代码处解决。发生异常并不表示代码中存在错误。
最佳实践
因此,如果您将运行时发生的特定情况视为要通知开发人员的错误(“开发人员,此情况表明某个地方存在错误,请修复代码。”)然后断言。如果断言检查代码的输入参数,则通常应在输入参数违反条件时向文档添加代码具有“未定义行为”的文档。
如果不是这样的情况的发生并不是您眼中的错误的迹象,而是您认为应该由客户端代码处理的(可能很少见但)可能的情况,请引发异常。引发异常的情况应该是相应代码文档的一部分。
使用时是否存在性能问题?
assert
断言的评估需要一些时间。不过,可以在编译时将其消除。但是,这会带来一些后果,请参见下文。
使用时是否存在代码维护问题
assert
断言通常可以提高代码的可维护性,因为它们可以通过使假设明确化并在运行时定期验证这些假设来提高可读性。这也将有助于捕获回归。但是,需要牢记一个问题:断言中使用的表达式应该没有副作用。如上所述,可以在编译时消除断言-这意味着潜在的副作用也将消失。这可以-意外地-更改代码的行为。
回答 11
声明将检查-1
.有效条件,
2.有效语句,
3.真实逻辑;
源代码。不会使整个项目失败,而是发出警报,指出源文件中不适当的内容。
在示例1中,由于变量’str’不为null。因此,不会引发任何断言或异常。
范例1:
#!/usr/bin/python
str = 'hello Python!'
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 Python!
FileName ..................... hello
FilePath ..................... C:/Python\hello.py
在示例2中,var’str’为null。因此,我们可以通过assert语句来挽救用户,使其免于出现错误的程序。
范例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
什么也不会得到打印
回答 12
在PTVS,PyCharm等IDE中,assert isinstance()
可以使用Wing 语句为一些不清楚的对象启用代码完成功能。
回答 13
对于它的价值,如果您要处理依靠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