【问题标题】:How do I know which contract failed with Python's contract.py?我如何知道 Python 的 contract.py 合约失败了?
【发布时间】:2009-06-19 13:35:32
【问题描述】:

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

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

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

【问题讨论】:

    标签: python design-by-contract


    【解决方案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])
    

    【讨论】:

      【解决方案2】:

      不修改他的代码,我觉得是不行的,不过既然是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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-10
        相关资源
        最近更新 更多