Как я узнаю, какой контракт не удался с Python's contract.py? - PullRequest
1 голос
/ 19 июня 2009

Я играю с 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) относится к номеру строки в цикле. init строка документа, содержащая утверждение:

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

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

Тем не менее, очень сложно открыть файл и сосчитать номера строк документации. У кого-нибудь есть более быстрое решение для определения, какой контракт потерпел неудачу?

(Обратите внимание, что в этом примере есть одно предварительное условие, поэтому оно очевидно, но возможно несколько предварительных условий).

Ответы [ 2 ]

1 голос
/ 13 июня 2012

Это старый вопрос, но я также могу ответить на него. Я добавил вывод, вы увидите его в комментарии # 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])
1 голос
/ 20 июня 2009

Без изменения его кода, я не думаю, что вы можете, но, поскольку это 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
...