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 会引发异常吗?
解决方案
当整个函数中 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
其他提示
断言应用于测试条件 永远不应该发生. 。目的是在程序状态损坏的情况下尽早崩溃。
异常应该用于可能发生的错误,并且 你几乎应该总是创建自己的异常类.
例如,如果您正在编写一个函数来从配置文件中读取数据 dict
, ,文件中的格式不正确应该会引发 ConfigurationSyntaxError
, ,虽然你可以 assert
你不会回来 None
.
在你的例子中,如果 x
是通过用户界面或外部源设置的值,例外是最好的。
如果 x
仅由您自己的代码在同一程序中设置,请使用断言。
优化编译时删除“assert”语句. 。所以,是的,性能和功能都存在差异。
当编译时请求优化时,当前代码生成器不会为断言语句发出任何代码。- Python 2.6.4 文档
如果你使用 assert
实现应用程序功能,然后优化生产部署,您将受到“but-it-works-in-dev”缺陷的困扰。
的四个目的 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实际上是他的。很多工作都丢掉了。
有了断言,无论谁错了,他们都会很快就能看到这是他们的错,而不是您的错。爱丽丝、伯恩德和你们都会受益。节省大量时间。告知您的代码的读者(包括您自己)您的代码在某个时刻取得了哪些成就。
假设您有一个条目列表,每个条目都可以很干净(很好),也可以是Smorsh,Trale,Gullup或Twinkled(这都是不可接受的)。如果它是smosh的,那么它必须是unsmoshed的;如果它是真实的,那么它一定是歪曲的;如果它是咕噜咕噜的,那么它必须小跑(然后可能也有节奏);如果它闪烁,除了星期四之外,它必须再次闪烁。你明白了:这是很复杂的事情。但最终结果是(或应该是)所有条目都是干净的。正确的事情(TM)是总结清洁循环的效果为assert(all(entry.isClean() for entry in mylist))
此陈述为每个试图了解什么的每个人都感到头疼 确切地 这是美妙的循环正在实现。这些人中最常见的可能就是你自己。
告诉计算机你的代码在某个时刻取得了什么成果。
如果您曾经忘记在小跑之后加快需要的条目,assert
将节省您的一天,并避免您的代码破裂,亲爱的达芙妮(Dear Daphne)。
在我脑海里, assert
文档(1和3)和保障(2和4)的两个目的同样有价值。
通知人们甚至可能是 更多的 比通知计算机有价值,因为它可以防止错误 assert
在任何情况下,旨在捕获(在情况1)和随后的许多错误。
除了其他答案之外,断言本身也会引发异常,但仅限于 AssertionErrors。从功利主义的角度来看,当您需要对捕获的异常进行细粒度控制时,断言不适合。
这种方法唯一真正错误的是,很难使用断言语句做出非常具有描述性的异常。如果您正在寻找更简单的语法,请记住您 能 也做这样的事情:
class XLessThanZeroException(Exception):
pass
def CheckX(x):
if x < 0:
raise XLessThanZeroException()
def foo(x):
CheckX(x)
#do stuff here
另一个问题是,使用断言进行正常条件检查会很难使用 -O 标志禁用调试断言。
正如前面所说,当您的代码不应该达到某个点(这意味着那里存在错误)时,应该使用断言。我认为使用断言的最有用的原因可能是不变/前置/后置条件。这些在循环或函数的每次迭代开始或结束时必须为真。
例如,递归函数(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
这些循环不变量通常可以用断言来表示。
英语单词 断言 这里的意思是 发誓, 确认, 承认. 。这并不意味着 “查看” 或者 “应该”. 。代表着 你 作为一名编码员正在制作 宣誓声明 这里:
# 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
如果代码正确,则禁止 单项赛事爆冷, 、硬件故障等, 没有任何断言会失败. 。这就是为什么程序对最终用户的行为不能受到影响。特别是,即使在以下情况下,断言也不会失败 特殊的计划条件. 。它只是永远不会发生。如果发生这种情况,程序员就应该受到惩罚。
是 有性能问题吗?
请记住 “先让它发挥作用,然后才能使其快速发挥作用”.
任何程序中很少有一部分与其速度相关。您始终可以剔除或简化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!
价格便宜,但可能会抓住大部分 实际的 无论如何,程序错误。
有一个框架叫JBoss 流口水 对于进行运行时监控以断言业务规则的java,这回答了问题的第二部分。不过我不确定python是否有这样的框架。
断言是检查 -
1.有效条件,
2.有效的声明,
3.真正的逻辑;
的源代码。它不会使整个项目失败,而是发出警报,表明源文件中存在某些不合适的内容。
在示例 1 中,由于变量 'str' 不为 nul。因此不会引发任何断言或异常。
示例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。因此,我们可以通过以下方式使用户免于继续执行错误的程序 断言 陈述。
示例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 断言语句.py
什么都不会打印
在 PTVS、PyCharm、Wing 等 IDE 中 assert isinstance()
语句可用于为一些不清楚的对象启用代码补全。
如果您正在处理依赖于的遗留代码 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