我正在玩contract.py,Terrence Way的Python合同设计参考实现。当违反合同(前提条件/后置条件/不变量)时,实现会抛出异常,但如果有多个与方法关联的合同,则它不会为您提供快速识别哪个特定合同失败的方法。
例如,如果我采用circbuf.py示例,并通过传递否定参数违反前提条件,如下所示:
circbuf(-5)
然后我得到一个如下所示的追溯:
Traceback (most recent call last):
File "circbuf.py", line 115, in <module>
circbuf(-5)
File "<string>", line 3, in __assert_circbuf___init___chk
File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1204, in call_constructor_all
File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1293, in _method_call_all
File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1332, in _call_all
File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1371, in _check_preconditions
contract.PreconditionViolationError: ('__main__.circbuf.__init__', 4)
我的预感是PreconditionViolationError(4)中的第二个参数引用了包含断言的circbuf。 init docstring中的行号:
def __init__(self, leng):
"""Construct an empty circular buffer.
pre::
leng > 0
post[self]::
self.is_empty() and len(self.buf) == leng
"""
然而,必须打开文件并计算文档字符串行号是一件痛苦的事。有没有人有更快的解决方案来确定哪个合同失败了?
(注意,在这个例子中,只有一个前置条件,所以很明显,但可能有多个前提条件。)
答案 0 :(得分:1)
不修改他的代码,我认为你不能,但因为这是python ......
如果你在寻找他向用户提出异常的地方,我认为可以将你正在寻找的信息推送到它...我不希望你能够得到回溯要好一点,因为代码实际上包含在注释块中,然后进行处理。
代码非常复杂,但这可能是一个要看的块 - 也许如果你转出一些args,你可以弄清楚它们是怎么回事......
def _check_preconditions(a, func, va, ka):
# ttw006: correctly weaken pre-conditions...
# ab002: Avoid generating AttributeError exceptions...
if hasattr(func, '__assert_pre'):
try:
func.__assert_pre(*va, **ka)
except PreconditionViolationError, args:
# if the pre-conditions fail, *all* super-preconditions
# must fail too, otherwise
for f in a:
if f is not func and hasattr(f, '__assert_pre'):
f.__assert_pre(*va, **ka)
raise InvalidPreconditionError(args)
# rr001: raise original PreconditionViolationError, not
# inner AttributeError...
# raise
raise args
# ...rr001
# ...ab002
# ...ttw006
答案 1 :(得分:1)
这是一个老问题,但我也可以回答它。我添加了一些输出,你会在#jlr001评论中看到它。将下面的行添加到contract.py中,当它引发异常时,它将显示文档行号和触发它的语句。没有比这更好的了,但它至少会阻止你猜测是什么条件触发了它。
def _define_checker(name, args, contract, path):
"""Define a function that does contract assertion checking.
args is a string argument declaration (ex: 'a, b, c = 1, *va, **ka')
contract is an element of the contracts list returned by parse_docstring
module is the containing module (not parent class)
Returns the newly-defined function.
pre::
isstring(name)
isstring(args)
contract[0] in _CONTRACTS
len(contract[2]) > 0
post::
isinstance(__return__, FunctionType)
__return__.__name__ == name
"""
output = StringIO()
output.write('def %s(%s):\n' % (name, args))
# ttw001... raise new exception classes
ex = _EXCEPTIONS.get(contract[0], 'ContractViolationError')
output.write('\tfrom %s import forall, exists, implies, %s\n' % \
(MODULE, ex))
loc = '.'.join([x.__name__ for x in path])
for c in contract[2]:
output.write('\tif not (')
output.write(c[0])
# jlr001: adding conidition statement to output message, easier debugging
output.write('): raise %s("%s", %u, "%s")\n' % (ex, loc, c[1], c[0]))
# ...ttw001
# ttw016: return True for superclasses to use in preconditions
output.write('\treturn True')
# ...ttw016
return _define(name, output.getvalue(), path[0])