我怎么知道Python的contract.py失败了哪个合同?

时间:2009-06-19 13:35:33

标签: python design-by-contract

我正在玩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
    """

然而,必须打开文件并计算文档字符串行号是一件痛苦的事。有没有人有更快的解决方案来确定哪个合同失败了?

(注意,在这个例子中,只有一个前置条件,所以很明显,但可能有多个前提条件。)

2 个答案:

答案 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])