我如何知道Python的contract.py哪个合约失败了?

发布于 2024-07-24 07:10:41 字数 1446 浏览 5 评论 0原文

我正在使用 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包含断言的文档字符串:

def __init__(self, leng):
    """Construct an empty circular buffer.

    pre::
        leng > 0
    post[self]::
        self.is_empty() and len(self.buf) == leng
    """

但是,必须打开文件并计算文档字符串行号是一件很痛苦的事情。 有人有更快的解决方案来识别哪个合同失败了吗?

(请注意,在此示例中,有一个前提条件,因此很明显,但也可以有多个前提条件)。

I'm playing with contract.py, Terrence Way's reference implementation of design-by-contract for Python. The implementation throws an exception when a contract (precondition/postcondition/invariant) is violated, but it doesn't provide you a quick way of identifying which specific contract has failed if there are multiple ones associated with a method.

For example, if I take the circbuf.py example, and violate the precondition by passing in a negative argument, like so:

circbuf(-5)

Then I get a traceback that looks like this:

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)

My hunch is that the second argument in the PreconditionViolationError (4) refers to the line number in the circbuf.init docstring that contains the assertion:

def __init__(self, leng):
    """Construct an empty circular buffer.

    pre::
        leng > 0
    post[self]::
        self.is_empty() and len(self.buf) == leng
    """

However, it's a pain to have to open the file and count the docstring line numbers. Does anybody have a quicker solution for identifying which contract has failed?

(Note that in this example, there's a single precondition, so it's obvious, but multiple preconditions are possible).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

沙与沫 2024-07-31 07:10:41

这是一个老问题,但我不妨回答一下。 我添加了一些输出,您将在注释 #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])

This is an old question but I may as well answer it. I added some output, you'll see it at the comment # jlr001. Add the line below to your contract.py and when it raises an exception it will show the doc line number and the statement that triggered it. Nothing more than that, but it will at least stop you from needing to guess which condition triggered it.

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])
泛泛之交 2024-07-31 07:10:41

如果不修改他的代码,我认为你不能,但因为这是 python...

如果你寻找他向用户引发异常的位置,我认为可以将你正在寻找的信息推送到其中...不过,我不希望您能够更好地进行回溯,因为代码实际上包含在注释块中,然后进行处理。

代码非常复杂,但这可能是一个值得一看的块 - 也许如果你转储一些参数,你可以弄清楚发生了什么......

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

Without modifying his code, I don't think you can, but since this is python...

If you look for where he raises the exception to the user, it I think is possible to push the info you're looking for into it... I wouldn't expect you to be able to get the trace-back to be any better though because the code is actually contained in a comment block and then processed.

The code is pretty complicated, but this might be a block to look at - maybe if you dump out some of the args you can figure out whats going on...

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文