2009-06-19 5 views
1

Je joue avec contract.py, l'implémentation de référence de Terrence Way de design-by-contract pour Python. L'implémentation génère une exception lorsqu'un contrat (précondition/postcondition/invariant) est violé, mais il ne vous fournit pas un moyen rapide d'identifier quel contrat spécifique a échoué s'il y en a plusieurs associés à une méthode.Comment savoir quel contrat a échoué avec contract.py de Python?

Par exemple, si je prends l'exemple circbuf.py, et viole la condition sine qua non en passant dans un argument négatif, comme ceci:

circbuf(-5) 

Puis-je obtenir un retraçage qui ressemble à ceci:

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) 

Mon intuition est que le deuxième argument dans PreconditionViolationError (4) se réfère au numéro de ligne dans le circbuf. init de docstring qui contient l'affirmation:

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

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

Cependant, il est une douleur d'avoir à ouvrir le fichier et compter les numéros de ligne docstring. Quelqu'un a-t-il une solution plus rapide pour identifier quel contrat a échoué?

(Notez que dans cet exemple, il y a une seule condition préalable, donc c'est évident, mais plusieurs conditions préalables sont possibles).

Répondre

1

Ceci est un vieux question mais je peux aussi bien y répondre. J'ai ajouté une sortie, vous le verrez au commentaire # jlr001. Ajoutez la ligne ci-dessous à votre contrat.py et quand il déclenche une exception, il affichera le numéro de ligne du document et l'instruction qui l'a déclenché. Rien de plus, mais cela vous empêchera au moins de deviner quelle condition l'a déclenchée.

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

Sans modifier son code, je ne pense pas que vous pouvez, mais puisque ce python est ...

Si vous cherchez où il soulève l'exception à l'utilisateur, il me semble possible de pousser le info que vous cherchez ... Je ne m'attendrais pas à ce que vous soyez en mesure d'obtenir la trace de retour, car le code est contenu dans un bloc de commentaire, puis traité.

Le code est assez compliqué, mais cela pourrait être un bloc à regarder - peut-être si vous videz quelques-unes des args vous pouvez comprendre ce qui se passe ...

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